%{
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sobjc-commons/defines.h>
#include <sobjc-commons/str_stream.h>
#include <sobjc-commons/str_list.h>
#include <sobjc-commons/builtin_sels.h>
#include <sobjc-commons/utils.h>
#include "context.h"
#include "command.h"
#include "scope.h"
#include "name.h"
#include "patch.h"
#include "protocol.h"
#include "type.h"

#define STORAGE_CLS_SPCR_TYPEDEF    1
#define STORAGE_CLS_SPCR_EXTERN     (1<<1)
#define STORAGE_CLS_SPCR_STATIC     (1<<2)
#define STORAGE_CLS_SPCR_AUTO       (1<<3)
#define STORAGE_CLS_SPCR_REGISTER   (1<<4)

#define TYPE_SPCR_CHAR          1
#define TYPE_SPCR_SHORT         (1<<1)
#define TYPE_SPCR_INT           (1<<2)
#define TYPE_SPCR_LONG          (1<<3)
#define TYPE_SPCR_LONG_LONG     (1<<4)
#define TYPE_SPCR_FLOAT         (1<<5)
#define TYPE_SPCR_DOUBLE        (1<<6)
#define TYPE_SPCR_SIGNED        (1<<7)
#define TYPE_SPCR_UNSIGNED      (1<<8)
#define TYPE_SPCR_VOID          (1<<9)

#define PROTOCOL_DEFINE_FORMAT "typedef struct objc_protocol %s;"

typedef enum {
  BOTH_NOT_OBJECT,
  OBJECT,
  CLASS_OBJECT,
  SAME_CLASS,
  COMPATIBLE_CLASS,
  INCOMPATIBLE_TYPE,
  INCOMPATIBLE_CLASS,
  UNDECLARED_INTERFACE_CLASSES,
  UNKNOWN_CLASSES,
  GENERIC_PTR,
  VARIABLE_ARG
} CLASS_VALIDATION_RESULT;

struct class_validation_result {
  int type;
  char *name;
};

struct protocol_iter {
  objc_class_t cls;
  BOOL check;
  int st;
};

int yylex();
static int yyparse();
void yyerror(const char *);
static void parse_error(const char *, ...);
static void parse_warn(const char *, ...);
static void parse_logv(const char *, const char *, va_list);
static void begin_scope();
static void end_scope();
static void set_func(objc_node_t, objc_node_t, objc_node_t);
static void validate_type_spcrs_and_type_quals(objc_node_t);
static void validate_type_spcr(int, unsigned int *);
static void validate_type_qual(int, unsigned int *);
static unsigned int type_spcr_tag_to_flag(int);
static void validate_storage_cls_spcr(int, unsigned int *);
static char *generate_kw_sel(objc_node_t);
static char *get_interface_name();
static void parse_protocol_name(size_t, char *);
static char *get_all_ivar_defs(objc_class_t, BOOL);
static void add_superclass_ivar_patch(size_t, objc_class_t);
static void add_class_struct_patch(size_t);
static void add_ivar_ref_patch(size_t, const char *);
static void validate_all_meth_defs();
static int validate_meth_defs(objc_class_t, BOOL, BOOL);
static int validate_protocol_methods(objc_class_t, objc_protocol_list_t, BOOL);
static int validate_protocol_method(char *, objc_protocol_method_t, struct protocol_iter *);
static void set_func_args(objc_node_t);
static void add_kw_sel_patches(BOOL, size_t, const char *, const char *);
static void add_lookup_imp_patches(size_t, size_t, size_t, size_t,
                                   const char *, objc_method_sig_t);
static objc_method_sig_t validate_receiver(const char *);
static objc_method_sig_t register_method_sig(objc_class_t, const char *, objc_node_t, BOOL);
static objc_method_sig_t register_protocol_method_sig(objc_protocol_t, const char *, objc_node_t, BOOL);
static void add_direct_binding_imp_patches(size_t, size_t,
                                           size_t, size_t,
                                           objc_method_t);
static void validate_class_type(struct class_validation_result *,
                                BOOL, objc_scope_t,
                                const char *, objc_node_t);
static void add_class_cast_patch(struct class_validation_result *,
                                 size_t, const char *);
static char *scan_objc_str(size_t, const char *);
static void add_rest_patches();

extern char yytext[];
extern FILE *yyin;

static FILE *parsing_file;
static const char *parsing_path;

static BOOL has_error            = NO;
static int found_typedef         = 0;
static char *method_name         = NULL;
static BOOL is_inst_meth         = NO;
static BOOL is_meth_def_scope    = NO;
static BOOL gen_sel_table        = NO;
static size_t cls_name_lc        = 0;
static size_t sel_table_lc       = 0;
static size_t ic_table_lc        = 0;
static size_t objc_str_table_lc  = 0;
static size_t kw_dclr_colon_lc   = 0;
static size_t kw_msg_dclr_close_lc = 0;
static BOOL kw_meth_first_brace  = NO;
static BOOL in_mret_type = NO;
static BOOL mtype_is_omitted;
static size_t last_compound_stmt_brace_lc;
static size_t recv_type_op_lc;
static char *recv_ctype;
static objc_ctype_info_t recv_info;
static char *ret_ctype           = NULL;
static objc_node_t func_args_node = NULL;
static objc_str_list_t objc_objc_strs;
static size_t objc_all_objc_strs_len = 0;

static objc_protocol_t current_protocol;
static BOOL protocol_opt_decls;

objc_scope_t objc_root_scope         = NULL;
objc_scope_t objc_current_scope      = NULL;
int objc_added_nlines                = 0;
st_table_t objc_objc_str_table       = NULL;
int objc_nobjc_strs                  = 0;
st_table_t objc_declared_class_table = NULL;
int objc_in_decl_spcr_list           = 0;
int objc_successes_decl_spcr         = 0;
unsigned long objc_nsend_sites       = 0;

%}

%union {
  objc_node_t node;
  const char *csval;
  char *sval;
  int ival;
  objc_str_list_t strs;
  size_t lc; /* location */
  struct {
    size_t lc; /* location */
    union {
      char *s;
      const char *cs;
      size_t len;
    } v;
  } range;
};

%token <range> IDENTIFIER TYPE_NAME CLASS_NAME DECL_CLASS_NAME
%token <range> STR_LIT INT_CONST FLOAT_CONST CHAR_CONST ENUM_CONST
%token <lc> SIZEOF PTR_OP INC_OP DEC_OP LEFT_OP RIGHT_OP
%token <lc> PLUS_OP MINUS_OP TIMES_OP DIV_OP MOD_OP
%token <lc> LT_OP GT_OP LE_OP GE_OP EQ_OP NE_OP
%token <lc> AND_OP OR_OP NOT_OP LAND_OP LOR_OP LNOT_OP XOR_OP
%token <lc> ASSIGN_OP MUL_ASSIGN_OP DIV_ASSIGN_OP MOD_ASSIGN_OP ADD_ASSIGN_OP
%token <lc> SUB_ASSIGN_OP LEFT_ASSIGN_OP RIGHT_ASSIGN_OP AND_ASSIGN_OP
%token <lc> XOR_ASSIGN_OP OR_ASSIGN_OP
%token <lc> LPAREN RPAREN LBRACE RBRACE LBRACK RBRACK
%token <lc> COMMA PERIOD SEMI COLON COLON2 ELLIPSIS

%token <lc> TYPEDEF EXTERN STATIC AUTO REGISTER
%token <lc> CHAR SHORT INT LONG UNSIGNED FLOAT DOUBLE VOID
%token <lc> STRUCT UNION ENUM ELIPSIS RANGE
%token <lc> SIGNED SIGNED2 SIGNED3
%token <lc> CONST CONST2 CONST3
%token <lc> VOLATILE VOLATILE2 VOLATILE3
%token <lc> INLINE INLINE2 INLINE3
%token <lc> EXTENSION FUNCTION PRETTY_FUNCTION

%token <lc> CASE DEFAULT IF ELSE SWITCH WHILE DO FOR GOTO CONTINUE BREAK RETURN

%token <range> CATEGORY CLASS COMPATIBILITY_ALIAS ENCODE END EXPORT INTERFACE IMPL IMPORT
%token <range> PRIVATE PROTECTED PROTOCOL PUBLIC SEL SUPER
%token <range> PUBLIC_QUAL PROTECTED_QUAL PRIVATE_QUAL SEALED ABSTRACT
%token <range> XSTR_LIT INSTANCE SELF OPTIONAL REQUIRED

%token GCC_ATTR GCC_ASM GCC_ASM2 GCC_ASM3

%type <node> abstract_declarator
%type <node> additive_expression
%type <node> and_expression
%type <node> argument_expression_list
%type <node> assignment_expression
%type <range> assignment_operator
%type <node> base_declarator
%type <node> cast_expression
%type <node> category_interface
%type <node> category_interface_declaration_list
%type <range> class_interface_specifier
%type <node> category_implementation
%type <range> category_name
%type <node> category_specifier
%type <node> class_declaration
%type <node> class_declarator_list
%type <node> class_interface
%type <node> class_interface_declaration
%type <node> class_implementation
%type <node> class_implementation_specifier
%type <range> class_name
%type <node> class_specifier
%type <node> compound_statement
%type <node> compatibility_alias 
%type <node> conditional_expression
%type <node> constant
%type <node> constant_expression
%type <node> declaration
%type <node> declaration_specifier
%type <node> declaration_specifier_list
%type <node> declarator
%type <node> declaration_list
%type <node> direct_abstract_declarator
%type <node> direct_declarator
%type <node> enumerator
%type <node> enumerator_list
%type <node> enum_specifier
%type <node> equality_expression
%type <node> exclusive_or_expression
%type <node> expression
%type <node> external_declaration
%type <node> expression_statement
%type <node> function_definition
%type <csval> gcc_asm
%type <node> gcc_asm_clobber_list
%type <node> gcc_asm_operand
%type <node> gcc_asm_operand_list
%type <node> gcc_asm_option_list
%type <node> gcc_asm_statement
%type <csval> gcc_asm_volatile
%type <node> gcc_assembly
%type <sval> gcc_attribute_name
%type <node> gcc_attribute_specifier
%type <node> gcc_direct_attribute_specifier
%type <node> gcc_direct_attribute_specifier_list
%type <node> identifier_list
%type <node> implementation_definition
%type <node> implementation_definition_list
%type <node> inclusive_or_expression
%type <node> init_declarator
%type <node> init_declarator_list
%type <node> initializer
%type <node> initializer_list
%type <node> instance_variable_declaration
%type <node> instance_variable_declaration_list
%type <node> instance_variable_list
%type <node> interface_declaration
%type <node> interface_declaration_list
%type <node> iteration_statement
%type <node> jump_statement
%type <node> keyword_argument_expression
%type <node> keyword_argument_expression_list
%type <node> keyword_declarator
%type <node> keyword_selector
%type <strs> keyword_selector_name
%type <node> labeled_statement
%type <node> logical_or_expression
%type <node> logical_and_expression
%type <node> method_declaration
%type <node> method_declarator
%type <node> method_definition
%type <node> method_qualifier
%type <node> method_qualifier_list
%type <node> method_selector
%type <node> method_specifier
%type <node> method_type
%type <node> message_expression
%type <node> multiplicative_expression
%type <node> parameter_declaration
%type <node> parameter_list
%type <node> parameter_type_list
%type <node> pointer
%type <node> postfix_expression
%type <node> primary_expression
%type <strs> protocol_confirm_list
%type <node> protocol_declaration
%type <node> protocol_declaration_list
%type <range> protocol_expression_name
%type <node> protocol_list
%type <range> protocol_name
%type <strs> protocol_name_list
%type <node> protocol_reference_list
%type <sval> protocol_reference_name
%type <node> receiver
%type <ival> receiver_type
%type <node> relational_expression
%type <node> selection_statement
%type <sval> selector_name
%type <node> shift_expression
%type <node> specifier_qualifier_list
%type <node> statement
%type <node> statement_list
%type <node> storage_class_specifier
%type <node> string
%type <node> struct_declarator
%type <node> struct_declarator_list
%type <node> struct_declaration
%type <node> struct_declaration_list
%type <range> struct_name
%type <ival> struct_or_union
%type <node> struct_or_union_specifier
%type <node> transaction_unit
%type <node> type_name
%type <node> type_specifier
%type <node> type_qualifier
%type <node> type_qualifier_list
%type <node> unary_expression
%type <csval> unary_operator
%type <node> visibility_specification

%start transaction_unit

%%

transaction_unit
    : external_declaration
    | transaction_unit external_declaration
    ;

external_declaration
    : function_definition       { $$ = NULL; }
    | declaration               { $$ = NULL; }
    | class_declaration         { $$ = $1; }
    | class_interface           { $$ = $1; objc_current_class = NULL; }
    | class_implementation      { $$ = $1; objc_current_class = NULL; }
    | category_interface        { $$ = $1; objc_current_class = NULL; }
    | category_implementation   { $$ = $1; objc_current_class = NULL; }
    | protocol_declaration
      {
        $$ = $1;
        objc_current_class = NULL;
        current_protocol = NULL;
      }
    | protocol_declaration_list
      {
        $$ = $1;
        current_protocol = NULL;
      }
    | compatibility_alias       { $$ = NULL; }
    ;

function_definition
    : declarator
      {
        set_func(NULL, $1, NULL);
      }
      compound_statement
    | declarator declaration_list
      {
        set_func(NULL, $1, $2);
      }
      compound_statement
    | declaration_specifier_list declarator
      {
        set_func($1, $2, NULL);
      }
      compound_statement
    | declaration_specifier_list declarator declaration_list
      {
        set_func($1, $2, $3);
      }
      compound_statement
    ;

declaration
    : declaration_specifier_list SEMI
      {
        $$ = objc_make_decl_node($1, NULL);
      }
    | declaration_specifier_list init_declarator_list SEMI
      {
        int i;
        objc_node_t init_dclr;
        char *dclr_name;

        /* patch for selector and inline cache tables */
        if (found_typedef && gen_sel_table && !sel_table_lc)
          sel_table_lc = $3;

        $$ = objc_make_decl_node($1, $2);
        for (i = 0; i < $2->a.l.nsubs; i++) {
          init_dclr = $2->a.l.subs[i];
          dclr_name = objc_node_dclr_name(init_dclr);
          if (found_typedef)
            objc_scope_set_type(objc_current_scope, $1, init_dclr, dclr_name);
          else
            objc_scope_set_var(objc_current_scope, $1, init_dclr, dclr_name);
        }
        found_typedef = 0;
      }
    ;

declaration_specifier_list
    : declaration_specifier
      {
        $$ = objc_make_decl_spcr_list_node($1);
      }
    | declaration_specifier declaration_specifier_list
      {
        objc_node_insert($2, $1);
        $$ = $2;
        validate_type_spcrs_and_type_quals($$);
      }
    ;

declaration_specifier
    : storage_class_specifier   { $$ = $1; }
    | type_specifier            { $$ = $1; }
    | type_qualifier            { $$ = $1; }
    ;

storage_class_specifier
    : TYPEDEF
      {
        found_typedef = 1;
        $$ = objc_make_storage_cls_spcr_node(TYPEDEF);
      }
    | EXTERN
      {
        $$ = objc_make_storage_cls_spcr_node(EXTERN);
      }
    | STATIC
      {
        $$ = objc_make_storage_cls_spcr_node(STATIC);
      }
    | AUTO
      {
        $$ = objc_make_storage_cls_spcr_node(AUTO);
      }
    | REGISTER
      {
        $$ = objc_make_storage_cls_spcr_node(REGISTER);
      }
    ;

type_specifier
    : CHAR
      {
        $$ = objc_make_type_spcr_node(CHAR);
      }
    | CHAR LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(CHAR);
        parse_error("cannot confirm protocol for `char'");
      }
    | SHORT
      {
        $$ = objc_make_type_spcr_node(SHORT);
      }
    | SHORT LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(SHORT);
        parse_error("cannot confirm protocol for `short'");
      }
    | INT
      {
        $$ = objc_make_type_spcr_node(INT);
      }
    | INT LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(INT);
        parse_error("cannot confirm protocol for `int'");
      }
    | LONG
      {
        $$ = objc_make_type_spcr_node(LONG);
      }
    | LONG LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(LONG);
        parse_error("cannot confirm protocol for `long'");
      }
    | SIGNED
      {
        $$ = objc_make_type_spcr_node(SIGNED);
      }
    | SIGNED LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(SIGNED);
        parse_error("cannot confirm protocol for `signed'");
      }
    | SIGNED2
      {
        $$ = objc_make_type_spcr_node(SIGNED2);
      }
    | SIGNED2 LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(SIGNED2);
        parse_error("cannot confirm protocol for `signed'");
      }
    | SIGNED3
      {
        $$ = objc_make_type_spcr_node(SIGNED3);
      }
    | SIGNED3 LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(SIGNED3);
        parse_error("cannot confirm protocol for `signed'");
      }
    | UNSIGNED
      {
        $$ = objc_make_type_spcr_node(UNSIGNED);
      }
    | UNSIGNED LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(UNSIGNED);
        parse_error("cannot confirm protocol for `unsigned'");
      }
     | FLOAT
      {
        $$ = objc_make_type_spcr_node(FLOAT);
      }
    | FLOAT LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(FLOAT);
        parse_error("cannot confirm protocol for `float'");
      }
    | DOUBLE
      {
        $$ = objc_make_type_spcr_node(DOUBLE);
      }
    | DOUBLE LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(DOUBLE);
        parse_error("cannot confirm protocol for `double'");
      }
    | VOID
      {
        $$ = objc_make_type_spcr_node(VOID);
      }
    | VOID LT_OP protocol_confirm_list GT_OP
      {
        $$ = objc_make_type_spcr_node(VOID);
        parse_error("cannot confirm protocol for `void'");
      }
    | struct_or_union_specifier
      {
        $$ = $1;
      }
    | enum_specifier
      {
        $$ = $1;
      }
    | TYPE_NAME
      {
        $$ = objc_make_typedef_name_node($1.v.s);
      }
    | TYPE_NAME LT_OP protocol_confirm_list GT_OP
      { 
        $$ = objc_make_typedef_name_node($1.v.s);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($4, 1, NULL);
      }
    | CLASS_NAME
      {
        $$ = objc_make_cls_name_node($1.v.s, NULL);
      }
    | CLASS_NAME LT_OP protocol_confirm_list GT_OP
      { 
        $$ = objc_make_cls_name_node($1.v.s, $3);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($4, 1, NULL);
      }
    | DECL_CLASS_NAME
      {
        $$ = objc_make_cls_name_node($1.v.s, NULL);
      }
    | DECL_CLASS_NAME LT_OP protocol_confirm_list GT_OP
      { 
        $$ = objc_make_cls_name_node($1.v.s, $3);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($4, 1, NULL);
      }
    | INSTANCE
      {
        $$ = objc_make_type_spcr_node(INSTANCE);
        if (in_mret_type)
          objc_add_patch($1.lc, $1.v.len, "id");
        else
          parse_error("'Instance' can be used only in method return type");
      }
    | SELF
      {
        $$ = objc_make_type_spcr_node(SELF);
        if (in_mret_type)
          objc_add_patch($1.lc, $1.v.len, "id");
        else
          parse_error("'Self' can be used only in method return type");
      }
    ;

protocol_confirm_list
    : protocol_reference_name
      {
        $$ = objc_make_str_list($1, NULL);
        if (!objc_get_protocol($1))
          parse_error("protocol `%s' is not declared", $1);
      }
    | protocol_confirm_list COMMA protocol_reference_name
      { 
        $$ = objc_str_list_add($1, $3);
        objc_add_patch($2, 1, NULL);
        if (!objc_get_protocol($3))
          parse_error("protocol `%s' is not declared", $3);
      }
    ;

type_qualifier
    : CONST         { $$ = objc_make_type_qual_node(CONST); }
    | CONST2        { $$ = objc_make_type_qual_node(CONST2); }
    | CONST3        { $$ = objc_make_type_qual_node(CONST3); }
    | VOLATILE      { $$ = objc_make_type_qual_node(VOLATILE); }
    | VOLATILE2     { $$ = objc_make_type_qual_node(VOLATILE2); }
    | VOLATILE3     { $$ = objc_make_type_qual_node(VOLATILE3); }
    | INLINE        { $$ = objc_make_type_qual_node(INLINE); }
    | INLINE2       { $$ = objc_make_type_qual_node(INLINE2); }
    | INLINE3       { $$ = objc_make_type_qual_node(INLINE3); }
    | EXTENSION     { $$ = objc_make_type_qual_node(EXTENSION); }
    ;

struct_or_union_specifier
    : struct_or_union struct_name LBRACE struct_declaration_list RBRACE
      {
        $$ = objc_make_su_spcr_node($1, $2.v.s, $4);
        objc_scope_set_struct(objc_current_scope, $$, $2.v.s);
        
        if (!objc_str_table_lc && STREQ($2.v.s, "objc_const_str"))
          objc_str_table_lc = $5;
        else if (!ic_table_lc && STREQ($2.v.s, "objc_inline_cache"))
          ic_table_lc = $5;
      }
    | struct_or_union LBRACE struct_declaration_list RBRACE
      {
        $$ = objc_make_su_spcr_node($1, NULL, $3);
      }
    | struct_or_union struct_name
      {
        $$ = objc_make_su_spcr_node($1, $2.v.s, NULL);
        objc_scope_set_struct(objc_current_scope, $$, $2.v.s);
      }
    ;

struct_or_union
    : STRUCT        { $$ = 1; }
    | UNION         { $$ = 0; }
    ;

struct_name
    : IDENTIFIER    { $$ = $1; }
    | TYPE_NAME     { $$ = $1; }
    ;

struct_declaration_list
    : struct_declaration
      {
        $$ = objc_make_struct_decl_list_node($1);
      }
    | struct_declaration_list struct_declaration
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

init_declarator_list
    : init_declarator
      {
        $$ = objc_make_init_dclr_list_node($1);
      }
    | init_declarator_list COMMA init_declarator
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

init_declarator
    : declarator
      {
        $$ = objc_make_init_dclr_node($1, NULL);
      }
    | declarator ASSIGN_OP initializer
      {
        $$ = objc_make_init_dclr_node($1, $3);
      }
    ;

struct_declaration
    : specifier_qualifier_list struct_declarator_list SEMI
      {
        $$ = objc_make_struct_decl_node($1, $2);
      }
    ;

specifier_qualifier_list
    : type_specifier
      {
        $$ = objc_make_spcr_qual_list_node($1);
      }
    | type_specifier specifier_qualifier_list
      {
        objc_node_insert($2, $1);
        $$ = $2;
        validate_type_spcrs_and_type_quals($$);
      }
    | type_qualifier
      {
        $$ = objc_make_spcr_qual_list_node($1);
      }
    | type_qualifier specifier_qualifier_list
      {
        objc_node_insert($2, $1);
        $$ = $2;
        validate_type_spcrs_and_type_quals($$);
      }
    ;

struct_declarator_list
    : struct_declarator
      {
        $$ = objc_make_struct_dclr_list_node($1);
      }
    | struct_declarator_list COMMA struct_declarator
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

struct_declarator
    : declarator
      {
        $$ = objc_make_struct_dclr_node($1, NULL);
      }
    | COLON constant_expression
      {
        $$ = objc_make_struct_dclr_node(NULL, $2);
      }
    | declarator COLON constant_expression
      {
        $$ = objc_make_struct_dclr_node($1, $3);
      }
    ;

enum_specifier
    : ENUM LBRACE enumerator_list RBRACE
      {
        int i;

        $$ = objc_make_enum_spcr_node(NULL, $3);
        for (i = 0; i < $3->a.l.nsubs; i++) {
          objc_scope_set_var(objc_current_scope, $$, NULL,
                             $3->a.l.subs[i]->a.enmr.name);
        }
      }
    | ENUM IDENTIFIER LBRACE enumerator_list RBRACE
      {
        int i;

        $$ = objc_make_enum_spcr_node($2.v.s, $4);
        objc_scope_set_struct(objc_current_scope, $$, $2.v.s);
        for (i = 0; i < $4->a.l.nsubs; i++) {
          objc_scope_set_var(objc_current_scope, $$, NULL,
                             $4->a.l.subs[i]->a.enmr.name);
        }
      }
    | ENUM IDENTIFIER
      {
        $$ = objc_make_enum_spcr_node($2.v.s, NULL);
        objc_scope_set_struct(objc_current_scope, $$, $2.v.s);
      }
    ;

enumerator_list
    : enumerator
      {
        $$ = objc_make_enum_list_node($1);
      }
    | enumerator_list COMMA enumerator
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

enumerator
    : IDENTIFIER
      {
        $$ = objc_make_enum_node($1.v.s, NULL);
      }
    | IDENTIFIER ASSIGN_OP constant_expression
      {
        $$ = objc_make_enum_node($1.v.s, $3);
      }
    ;

declarator
    : base_declarator
      {
        $$ = objc_make_dclr_node($1, NULL, NULL);
      }
    | base_declarator gcc_assembly
      {
        $$ = objc_make_dclr_node($1, NULL, $2);
      }
    | base_declarator gcc_attribute_specifier 
      {
        $$ = objc_make_dclr_node($1, $2, NULL);
      }
    | base_declarator gcc_assembly gcc_attribute_specifier 
      {
        $$ = objc_make_dclr_node($1, $3, $2);
      }
    | base_declarator gcc_attribute_specifier gcc_assembly 
      {
        $$ = objc_make_dclr_node($1, $2, $3);
      }
    | gcc_attribute_specifier base_declarator
      {
        $$ = objc_make_dclr_node($2, $1, NULL);
      }
    | gcc_attribute_specifier base_declarator gcc_assembly
      {
        $$ = objc_make_dclr_node($2, $1, $3);
      }
    ;

base_declarator
    : direct_declarator
      {
        $$ = objc_make_base_dclr_node(NULL, $1);
      }
    | pointer direct_declarator
      {
        $$ = objc_make_base_dclr_node($1, $2);
      }
    ;

direct_declarator
    : IDENTIFIER
      {
        if (is_meth_def_scope)
          add_ivar_ref_patch($1.lc, $1.v.s);
        else if (found_typedef && !gen_sel_table && STREQ($1.v.s, "SEL"))
          gen_sel_table = YES;
        $$ = objc_make_ident_node($1.v.s);
      }
    | LPAREN declarator RPAREN
      {
        $$ = $2;
      }
    | direct_declarator LBRACK RBRACK
      {
        $$ = objc_make_array_dir_dclr_node($1, NULL);
      }
    | direct_declarator LBRACK constant_expression RBRACK
      {
        $$ = objc_make_array_dir_dclr_node($1, $3);
      }
    | direct_declarator LPAREN RPAREN
      {
        $$ = objc_make_func_dir_dclr_node($1, NULL);
      }
    | direct_declarator LPAREN parameter_type_list RPAREN
      {
        $$ = objc_make_func_dir_dclr_node($1, $3);
      }
    | direct_declarator LPAREN identifier_list RPAREN
      {
        $$ = objc_make_func_dir_dclr_node($1, $3);
      }
    ;

pointer
    : TIMES_OP
      {
        $$ = objc_make_ptr_node(NULL, NULL, NULL);
      }
    | TIMES_OP type_qualifier_list
      {
        $$ = objc_make_ptr_node(NULL, $2, NULL);
      }
    | TIMES_OP pointer
      {
        $$ = objc_make_ptr_node(NULL, NULL, $2);
      }
    | TIMES_OP type_qualifier_list pointer
      {
        $$ = objc_make_ptr_node(NULL, $2, $3);
      }
    | TIMES_OP gcc_attribute_specifier
      {
        $$ = objc_make_ptr_node($2, NULL, NULL);
      }
    | TIMES_OP gcc_attribute_specifier pointer
      {
        $$ = objc_make_ptr_node($2, $3, NULL);
      }
    | TIMES_OP gcc_attribute_specifier type_qualifier_list pointer
      {
        $$ = objc_make_ptr_node($2, $3, $4);
      }
    ;

type_qualifier_list
    : type_qualifier
      {
        $$ = objc_make_type_qual_list_node($1);
      }
    | type_qualifier_list type_qualifier
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

parameter_type_list
    : parameter_list
      {
        $$ = objc_make_param_type_list_node($1, 0);
      }
    | parameter_list COMMA ELLIPSIS
      {
        $$ = objc_make_param_type_list_node($1, 1);
      }
    ;

parameter_list
    : parameter_declaration
      {
        $$ = objc_make_param_list_node($1);
      }
    | parameter_list COMMA parameter_declaration
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

parameter_declaration
    : declaration_specifier_list
      {
        $$ = objc_make_param_decl_node($1, NULL);
      }
    | declaration_specifier_list declarator
      {
        $$ = objc_make_param_decl_node($1, $2);
      }
    | declaration_specifier_list abstract_declarator
      {
        $$ = objc_make_param_decl_node($1, $2);
      }
    ;

identifier_list
    : IDENTIFIER
      {
        $$ = objc_make_ident_list_node($1.v.s);
        if (is_meth_def_scope)
          add_ivar_ref_patch($1.lc, $1.v.s);
      }
    | identifier_list COMMA IDENTIFIER
      {
        $1->a.strs = objc_str_list_add($1->a.strs, $3.v.s);
        $$ = $1;
        if (is_meth_def_scope)
          add_ivar_ref_patch($3.lc, $3.v.s);
      }
    ;

initializer
    : assignment_expression                 { $$ = $1; }
    | LBRACE initializer_list RBRACE        { $$ = objc_make_intr_node($2); }
    | LBRACE initializer_list COMMA RBRACE  { $$ = objc_make_intr_node($2); }
    ;

initializer_list
    : initializer
      {
        $$ = objc_make_intr_list_node($1);
      }
    | initializer_list COMMA initializer
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

type_name
    : specifier_qualifier_list
      {
        $$ = objc_make_type_name_node($1, NULL);
      }
    | specifier_qualifier_list abstract_declarator
      {
        $$ = objc_make_type_name_node($1, $2);
      }
    ;

abstract_declarator
    : pointer
      {
        $$ = objc_make_abs_dclr_node($1, NULL);
      }
    | direct_abstract_declarator
      {
        $$ = objc_make_abs_dclr_node(NULL, $1);
      }
    | pointer direct_abstract_declarator
      {
        $$ = objc_make_abs_dclr_node($1, $2);
      }
    ;

direct_abstract_declarator
    : LPAREN abstract_declarator RPAREN
      {
        $$ = $2;
      }
    | LBRACK RBRACK
      {
        $$ = objc_make_const_dir_abs_dclr_node(NULL, NULL);
      }
    | LBRACK constant_expression RBRACK
      {
        $$ = objc_make_const_dir_abs_dclr_node(NULL, $2);
      }
    | direct_abstract_declarator LBRACK RBRACK
      {
        $$ = objc_make_const_dir_abs_dclr_node($1, NULL);
      }
    | direct_abstract_declarator LBRACK constant_expression RBRACK
      {
        $$ = objc_make_const_dir_abs_dclr_node($1, $3);
      }
    | LPAREN RPAREN
      {
        $$ = objc_make_param_dir_abs_dclr_node(NULL, NULL);
      }
    | LPAREN parameter_type_list RPAREN
      {
        $$ = objc_make_param_dir_abs_dclr_node(NULL, $2);
      }
    | direct_abstract_declarator LPAREN RPAREN
      {
        $$ = objc_make_param_dir_abs_dclr_node($1, NULL);
      }
    | direct_abstract_declarator LPAREN parameter_type_list RPAREN
      {
        $$ = objc_make_param_dir_abs_dclr_node($1, $3);
      }
    ;

statement
    : labeled_statement     { $$ = $1; }
    | expression_statement  { $$ = $1; }
    | compound_statement    { $$ = $1; }
    | selection_statement   { $$ = $1; }
    | iteration_statement   { $$ = $1; }
    | jump_statement        { $$ = $1; }
    | gcc_asm_statement     { $$ = $1; }
    ;

labeled_statement
    : IDENTIFIER labeled_statement_separator statement
      {
        $$ = objc_make_label_stmt_node(0, NULL, $3);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | CASE constant_expression labeled_statement_separator statement
      {
        $$ = objc_make_label_stmt_node(CASE, $2, $4);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | DEFAULT labeled_statement_separator statement
      {
        $$ = objc_make_label_stmt_node(DEFAULT, NULL, $3);
        $$->scope = objc_current_scope;
        end_scope();
      }
    ;

labeled_statement_separator
    : COLON { begin_scope(); }
    ;

expression_statement
    : SEMI               { $$ = objc_make_expr_stmt_node(NULL); }
    | expression SEMI    { $$ = objc_make_expr_stmt_node($1); }
    ;

compound_statement
    : LBRACE RBRACE
      {
        if (kw_meth_first_brace)
          kw_meth_first_brace = NO;
        $$ = objc_make_compound_stmt_node(NULL, NULL);
        last_compound_stmt_brace_lc = $2;
      }
    | begin_compound_statement statement_list RBRACE
      {
        $$ = objc_make_compound_stmt_node(NULL, $2);
        $$->scope = objc_current_scope;
        end_scope();
        last_compound_stmt_brace_lc = $3;
      }
    | begin_compound_statement declaration_list RBRACE
      {
        $$ = objc_make_compound_stmt_node($2, NULL);
        $$->scope = objc_current_scope;
        end_scope();
        last_compound_stmt_brace_lc = $3;
      }
    | begin_compound_statement declaration_list statement_list RBRACE
      {
        $$ = objc_make_compound_stmt_node($2, $3);
        $$->scope = objc_current_scope;
        end_scope();
        last_compound_stmt_brace_lc = $4;
      }
    ;

begin_compound_statement
    : LBRACE
      {
        begin_scope();
        if (kw_meth_first_brace) {
          objc_add_patch($1, 1, "{ void *" RECV_VAR
                         " __attribute__((unused));");
          kw_meth_first_brace = NO;
        } else
          objc_add_patch($1, 1, "{ void *" RECV_VAR
                         " __attribute__((unused));");
      }
    ;

statement_list
    : statement
      {
        $$ = objc_make_stmt_list_node($1);
      }
    | statement_list statement
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

selection_statement
    : IF begin_selection_statement expression RPAREN statement
      {
        $$ = objc_make_select_stmt_node(IF, $3, $5, NULL);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | IF begin_selection_statement expression RPAREN statement ELSE statement
      {
        $$ = objc_make_select_stmt_node(IF, $3, $5, $7);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | SWITCH begin_selection_statement expression RPAREN statement
      {
        $$ = objc_make_select_stmt_node(SWITCH, $3, $5, NULL);
        $$->scope = objc_current_scope;
        end_scope();
      }
    ;

begin_selection_statement
    : LPAREN { begin_scope(); }
    ;

iteration_statement
    : WHILE begin_iteration_statement expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(WHILE, NULL, $3, NULL, $5);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | DO statement WHILE begin_iteration_statement expression RPAREN SEMI
      {
        $$ = objc_make_iter_stmt_node(DO, NULL, $5, NULL, $2);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement SEMI SEMI RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, NULL, NULL, NULL, $6);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement SEMI SEMI expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, NULL, NULL, $5, $7);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement SEMI expression SEMI RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, NULL, $4, NULL, $7);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement SEMI expression SEMI expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, NULL, $4, $6, $8);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement expression SEMI SEMI RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, $3, NULL, NULL, $7);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement expression SEMI SEMI expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, $3, NULL, $6, $8);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement expression SEMI expression SEMI RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, $3, $5, NULL, $8);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement expression SEMI expression SEMI expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, $3, $5, $7, $9);
        $$->scope = objc_current_scope;
        end_scope();
      }
    ;

begin_iteration_statement
    : LPAREN { begin_scope(); }
    ;

jump_statement
    : GOTO IDENTIFIER SEMI
      {
        $$ = objc_make_jump_stmt_node(GOTO, NULL, $2.v.s);
      }
    | CONTINUE SEMI
      {
        $$ = objc_make_jump_stmt_node(CONTINUE, NULL, NULL);
      }
    | BREAK SEMI
      {
        $$ = objc_make_jump_stmt_node(BREAK, NULL, NULL);
      }
    | RETURN SEMI
      {
        $$ = objc_make_jump_stmt_node(RETURN, NULL, NULL);
      }
    | RETURN expression SEMI
      {
        struct class_validation_result res;

        $$ = objc_make_jump_stmt_node(RETURN, $2, NULL);

        /* validate class type */
        validate_class_type(&res, is_inst_meth,
                            objc_current_scope,
                            ret_ctype, $2);
        add_class_cast_patch(&res, $1+6, "return");
      }
    ;

expression
    : assignment_expression
      {
        $$ = objc_make_expr_node($1);
      }
    | expression COMMA assignment_expression
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

assignment_expression
    : conditional_expression { $$ = $1; }
    | unary_expression assignment_operator assignment_expression
      {
        struct class_validation_result res;
        char *left;

        $$ = objc_make_assign_expr_node($1, $2.v.cs, $3);

        /* validate class type */
        if (STREQ($2.v.cs, "=")) {
          left = objc_ctype_from_expr(objc_current_scope, $1);
          validate_class_type(&res, is_inst_meth,
                              objc_current_scope,
                              left, $3);
          add_class_cast_patch(&res, $2.lc+1, "assignment");
        }
      }
    ;

assignment_operator
    : ASSIGN_OP        { $$.lc = $1; $$.v.cs = "="; }
    | MUL_ASSIGN_OP    { $$.lc = $1; $$.v.cs = "*="; }
    | DIV_ASSIGN_OP    { $$.lc = $1; $$.v.cs = "/="; }
    | MOD_ASSIGN_OP    { $$.lc = $1; $$.v.cs = "%="; }
    | ADD_ASSIGN_OP    { $$.lc = $1; $$.v.cs = "+="; }
    | SUB_ASSIGN_OP    { $$.lc = $1; $$.v.cs = "-="; }
    | LEFT_ASSIGN_OP   { $$.lc = $1; $$.v.cs = "<<="; }
    | RIGHT_ASSIGN_OP  { $$.lc = $1; $$.v.cs = ">>="; }
    | AND_ASSIGN_OP    { $$.lc = $1; $$.v.cs = "&="; }
    | XOR_ASSIGN_OP    { $$.lc = $1; $$.v.cs = "^="; }
    | OR_ASSIGN_OP     { $$.lc = $1; $$.v.cs = "|="; }
    ;

conditional_expression
    : logical_or_expression { $$ = $1; }
    | logical_or_expression '?' logical_or_expression COLON conditional_expression
      {
        $$ = objc_make_cond_expr_node($1, $3, $5);
      }
    ;

constant_expression
    : conditional_expression    { $$ = $1; }
    ;

logical_or_expression
    : logical_and_expression    { $$ = $1; }
    | logical_or_expression LOR_OP logical_and_expression
      {
        $$ = objc_make_log_or_expr_node($1, $3);
      }
    ;

logical_and_expression
    : inclusive_or_expression   { $$ = $1; }
    | logical_and_expression LAND_OP inclusive_or_expression
      {
        $$ = objc_make_log_and_expr_node($1, $3);
      }
    ;

inclusive_or_expression
    : exclusive_or_expression   { $$ = $1; }
    | inclusive_or_expression OR_OP exclusive_or_expression
      {
        $$ = objc_make_incl_or_expr_node($1, $3);
      }
    ;

exclusive_or_expression
    : and_expression    { $$ = $1; }
    | exclusive_or_expression XOR_OP and_expression
      {
        $$ = objc_make_exc_or_expr_node($1, $3);
      }
    ;

and_expression
    : equality_expression   { $$ = $1; }
    | and_expression AND_OP equality_expression
      {
        $$ = objc_make_and_expr_node($1, $3);
      }
    ;

equality_expression
    : relational_expression { $$ = $1; }
    | equality_expression EQ_OP relational_expression
      {
        $$ = objc_make_eq_expr_node($1, "==", $3);
      }
    | equality_expression NE_OP relational_expression
      {
        $$ = objc_make_eq_expr_node($1, "!=", $3);
      }
    ;

relational_expression
    : shift_expression  { $$ = $1; }
    | relational_expression LT_OP shift_expression
      {
        $$ = objc_make_rel_expr_node($1, "<", $3);
      }
    | relational_expression GT_OP shift_expression
      {
        $$ = objc_make_rel_expr_node($1, ">", $3);
      }
    | relational_expression LE_OP shift_expression
      {
        $$ = objc_make_rel_expr_node($1, "<=", $3);
      }
    | relational_expression GE_OP shift_expression
      {
        $$ = objc_make_rel_expr_node($1, ">=", $3);
      }
    ;

shift_expression
    : additive_expression   { $$ = $1; }
    | shift_expression LEFT_OP additive_expression
      {
        $$ = objc_make_shift_expr_node($1, "<<", $3);
      }
    | shift_expression RIGHT_OP additive_expression
      {
        $$ = objc_make_shift_expr_node($1, ">>", $3);
      }
    ;

additive_expression
    : multiplicative_expression { $$ = $1; }
    | additive_expression PLUS_OP multiplicative_expression
      {
        $$ = objc_make_add_expr_node($1, "+", $3);
      }
    | additive_expression MINUS_OP multiplicative_expression
      {
        $$ = objc_make_add_expr_node($1, "-", $3);
      }
    ;

multiplicative_expression
    : cast_expression   { $$ = $1; }
    | multiplicative_expression TIMES_OP cast_expression
      {
        $$ = objc_make_multi_expr_node($1, "*", $3);
      }
    | multiplicative_expression DIV_OP cast_expression
      {
        $$ = objc_make_multi_expr_node($1, "/", $3);
      }
    | multiplicative_expression MOD_OP cast_expression
      {
        $$ = objc_make_multi_expr_node($1, "%", $3);
      }
    ;

cast_expression
    : unary_expression  { $$ = $1; }
    | LPAREN type_name RPAREN cast_expression
      {
        $$ = objc_make_cast_expr_node($2, $4);
      }
    ;

unary_expression
    : postfix_expression        { $$ = $1; }
    | INC_OP unary_expression
      {
        $$ = objc_make_inc_or_dec_expr_node(1, "++", $2);
      }
    | DEC_OP unary_expression   
      {
        $$ = objc_make_inc_or_dec_expr_node(1, "--", $2);
      }
    | unary_operator cast_expression
      {
        $$ = objc_make_inc_or_dec_expr_node(1, $1, $2);
      }
    | SIZEOF unary_expression
      {
        $$ = objc_make_size_expr_node($2);
      }
    | SIZEOF LPAREN type_name RPAREN
      {
        $$ = objc_make_size_type_expr_node($3);
      }
    | SEL LPAREN selector_name RPAREN
      {
        static char buf[256];

        $$ = objc_make_sel_expr_node($3);

        if (!objc_is_sel_name_declared($3) && objc_main_opts.w_undeclared_sel)
          parse_warn("undeclared selector '%s'", $3);

        objc_register_sel_name($3, NO, YES);
        sprintf(buf, SEL_TABLE_VAR "[%lu]", objc_get_sel_num($3));
        objc_add_patch($1.lc, $1.v.len, buf);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($4, 1, NULL);
      }
    | PROTOCOL LPAREN protocol_expression_name RPAREN
      {
        $$ = objc_make_prtc_expr_node($3.v.s);
        if (objc_get_protocol($3.v.s)) {
          objc_add_patch($1.lc, $1.v.len, "objc_lookup_protocol");
          objc_add_patch($2, 1, "(\"");
          objc_add_patch($4, 1, "\")");
        } else
          parse_error("cannot find protocol declaration for `%s'", $3.v.s);
      }
    | ENCODE LPAREN type_name RPAREN
      {
        objc_type_encoding_t type;
        char *s;

        $$ = objc_make_enc_expr_node($3);
        type = objc_make_type_encoding_from_node($3);
        s = ALLOC_ATOM_N(char, strlen(type->full_code)+3);
        sprintf(s, "\"%s\"", type->full_code);
        objc_add_patch($1.lc, $1.v.len, s);
        objc_add_patch($2, 1, "/* ");
        objc_add_patch($4, 1, " */");
      }
    | message_expression        { $$ = $1; }
    ;

protocol_expression_name 
    : IDENTIFIER        { $$ = $1; }
    | CLASS_NAME        { $$ = $1; }
    | DECL_CLASS_NAME   { $$ = $1; }
    ;

unary_operator
    : AND_OP        { $$ = "&"; }
    | TIMES_OP      { $$ = "*"; }
    | PLUS_OP       { $$ = "+"; }
    | MINUS_OP      { $$ = "-"; }
    | NOT_OP        { $$ = "~"; }
    | LNOT_OP       { $$ = "!"; }
    ;

selector_name
    : IDENTIFIER
      {
        $$ = objc_strdup($1.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    | keyword_selector_name
      {
        $$ = objc_str_list_join($1, ":");
      }
    ;

keyword_selector_name
    : IDENTIFIER COLON
      {
        $$ = objc_make_str_list($1.v.s, NULL);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
      }
    | keyword_selector_name IDENTIFIER COLON
      {
        $$ = objc_str_list_add($1, $2.v.s);
        objc_add_patch($2.lc, strlen($2.v.s), NULL);
        objc_add_patch($3, 1, NULL);
      }
    ;

postfix_expression
    : primary_expression    { $$ = $1; }
    | postfix_expression LBRACK expression RBRACK
      {
        $$ = objc_make_array_ref_expr_node($1, $3);
      }
    | postfix_expression LPAREN RPAREN
      {
        $$ = objc_make_func_call_expr_node($1, NULL);
      }
    | postfix_expression LPAREN argument_expression_list RPAREN
      {
        struct class_validation_result res;
        int i;
        char *func_types, *arg_type, *errmsg;
        objc_node_t expr;

        errmsg = ALLOC_ATOM_N(char, 32);
        func_types = objc_ctype_from_expr(objc_current_scope, $1);
        for (i = 0; i < $3->a.l.nsubs; i++) {
          sprintf(errmsg, "passing argument %d", i+1);

          expr = $3->a.l.subs[i];
          arg_type = objc_ctype_get_arg_type(func_types, i);
          validate_class_type(&res, is_inst_meth,
                              objc_current_scope,
                              arg_type, expr);
          add_class_cast_patch(&res, i==0?$2+1:expr->lc+1, errmsg);
        }
        $$ = objc_make_func_call_expr_node($1, $3);
      }
    | postfix_expression PERIOD IDENTIFIER
      {
        $$ = objc_make_su_ref_expr_node($1, 1, $3.v.s);
      }
    | postfix_expression PTR_OP IDENTIFIER
      {
        $$ = objc_make_su_ref_expr_node($1, 0, $3.v.s);
      }
    | postfix_expression INC_OP
      {
        $$ = objc_make_inc_or_dec_expr_node(0, "++", $1);
      }
    | postfix_expression DEC_OP
      {
        $$ = objc_make_inc_or_dec_expr_node(0, "--", $1);
      }
    ;

primary_expression
    : IDENTIFIER
      {
        $$ = objc_make_ident_node($1.v.s);
        if (is_meth_def_scope)
          add_ivar_ref_patch($1.lc, $1.v.s);
      }
    | constant                  { $$ = $1; }
    | string                    { $$ = $1; }
    | LPAREN expression RPAREN  { $$ = $2; }
    ;

argument_expression_list
    : assignment_expression
      {
        $$ = objc_make_arg_expr_list_node($1);
      }
    | argument_expression_list COMMA assignment_expression
      {
        objc_node_add($1, $3);
        $$ = $1;
        $3->lc = $2;
      }
    ;

constant
    : INT_CONST     { $$ = objc_make_const_node(INT_CONST, $1.v.s); }
    | FLOAT_CONST   { $$ = objc_make_const_node(FLOAT_CONST, $1.v.s); }
    | CHAR_CONST    { $$ = objc_make_const_node(CHAR_CONST, $1.v.s); }
    | ENUM_CONST    { $$ = objc_make_const_node(ENUM_CONST, $1.v.s); }
    ;

string
    : STR_LIT           { $$ = objc_make_str_node($1.v.s); }
    | FUNCTION          { /* FIXME */ $$ = objc_make_str_node("__FUNCTION__"); }
    | PRETTY_FUNCTION   { /* FIXME */ $$ = objc_make_str_node("__PRETTY_FUNCTION__"); }
    | XSTR_LIT
      {
#define FORMAT "((%s *)(" OBJC_STR_TABLE_VAR "[%d].isa?" \
  "&" OBJC_STR_TABLE_VAR "[%d]:" \
  "(" OBJC_STR_TABLE_VAR "[%d].isa=objc_lookup_class(" \
  "_objc_const_str_class_name), &" \
  OBJC_STR_TABLE_VAR "[%d])))"

        static char *repl;
        const char *name;
        char *body;
        int n;

        body = scan_objc_str($1.lc, $1.v.s);
        if (!st_lookup(objc_objc_str_table, body, (st_data_t *)&n)) {
          n = objc_nobjc_strs++;
          st_insert(objc_objc_str_table, body, (st_data_t)n);
          objc_all_objc_strs_len += strlen(body);
          objc_objc_strs = objc_str_list_add(objc_objc_strs, body);
        }
        $$ = objc_make_objc_str_node(body, n);

        name = objc_get_const_str_class_name();
        if (!repl)
          repl = ALLOC_ATOM_N(char, strlen(FORMAT)+strlen(name)+64);
        sprintf(repl, FORMAT, name, n, n, n, n);
        objc_add_patch($1.lc, 1, repl);
#undef FORMAT
      }
    ;

declaration_list
    : declaration
      {
        $$ = objc_make_decl_list_node($1);
      }
    | declaration_list declaration
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

message_expression
    : LBRACK receiver IDENTIFIER RBRACK
      {
        objc_method_sig_t sig;

        $$ = objc_make_unary_msg_expr_node($2, $3.v.s, recv_info->is_inst);
        $$->a.unary_msg_expr.num = objc_nsend_sites;
        objc_register_sel_name($3.v.s, NO, YES);

        sig = validate_receiver($3.v.s);
        if (sig)
          add_lookup_imp_patches($1, $4, $3.lc, strlen($3.v.s), $3.v.s, sig);
        else
          objc_print_error("method signature not found `%s'", $3.v.s);
      }
    | LBRACK receiver keyword_argument_expression_list RBRACK
      {
        int i;
        objc_str_stream_t s;
        char *func_types, *arg_type, *errmsg, *sel;
        objc_method_sig_t sig;
        objc_node_t arg;
        size_t kw_arg_lc = 0, kw_arg_len = 0;
        struct class_validation_result res;

        s = objc_make_str_stream();
        for (i = 0; i < $3->a.l.nsubs; i++)
          objc_str_stream_write(s, $3->a.l.subs[i]->a.kw_arg_expr.name);
        sel = objc_str_stream_value(s);
        objc_register_sel_name(sel, NO, YES);

        $$ = objc_make_kw_msg_expr_node($2, sel,
                                        $3, recv_info->is_inst);
        $$->a.kw_msg_expr.num = objc_nsend_sites;

        sig = validate_receiver(sel);
        if (sig) {
          /* patch and validate */
          func_types = objc_method_sig_make_imp_ctype(sig, recv_ctype,
            recv_info->cls, recv_info->is_inst);
          errmsg = ALLOC_ATOM_N(char, 32);
          for (i = 0; i < $3->a.l.nsubs; i++) {
            arg = $3->a.l.subs[i];

            /* validate */
            sprintf(errmsg, "passing argument %d", i+1);
            arg_type = objc_ctype_get_arg_type(func_types, i+2);
            validate_class_type(&res, is_inst_meth,
                                objc_current_scope,
                                arg_type, arg);
            add_class_cast_patch(&res, arg->a.kw_arg_expr.colon_lc+1, errmsg);

            /* patch */
            if (i == 0) {
              kw_arg_lc = arg->a.kw_arg_expr.name_lc;
              kw_arg_len = strlen(arg->a.kw_arg_expr.name)-1;
              objc_add_patch(arg->a.kw_arg_expr.colon_lc, 1, ", ");
            } else {
              objc_add_patch(arg->a.kw_arg_expr.name_lc,
                             strlen(arg->a.kw_arg_expr.name)-1, ", ");
                objc_add_patch(arg->a.kw_arg_expr.colon_lc, 1, NULL);
            }
          }
          add_lookup_imp_patches($1, $4, kw_arg_lc, kw_arg_len, sel, sig);
        } else
          objc_print_error("method signature not found `%s'", sel);
      }
    ;

receiver
    : expression
      {
        $$ = $1;

        recv_ctype = objc_ctype_from_expr(objc_current_scope, $$);
        if (!objc_ctype_is_object(recv_ctype))
          parse_error("receiver is not object");
        else
          recv_info = objc_ctype_get_info(recv_ctype,
                                          objc_current_class,
                                          is_inst_meth);
      }
    | CLASS_NAME
      {
        char *repl;

        $$ = objc_make_cls_obj_recv_node($1.v.s);
        repl = ALLOC_ATOM_N(char, strlen($1.v.s) + 24);
        sprintf(repl, "objc_lookup_class(\"%s\")", $1.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), repl);

        recv_ctype = objc_ctype_from_expr(objc_current_scope, $$);
        recv_info = objc_ctype_get_info(recv_ctype,
                                        objc_current_class,
                                        is_inst_meth);
      }
    | DECL_CLASS_NAME
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        recv_ctype = NULL;
        recv_info = NULL;
      }
    | SUPER
      {
        $$ = objc_make_super_node();
        objc_add_patch($1.lc, $1.v.len, "(id)self");

        recv_ctype = objc_ctype_from_expr(objc_current_scope, $$);
        recv_info = objc_ctype_get_info(recv_ctype,
                                        objc_current_class,
                                        is_inst_meth);
      }
    ;

keyword_argument_expression_list
    : keyword_argument_expression
      {
        $$ = objc_make_kw_arg_expr_list_node($1);
      }
    | keyword_argument_expression_list keyword_argument_expression
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

keyword_argument_expression
    : IDENTIFIER COLON argument_expression_list
      {
        size_t len;

        len = strlen($1.v.s);
        $1.v.s[len] = ':';
        $1.v.s[len+1] = 0;
        $$ = objc_make_kw_arg_expr_node($1.lc, $2, $1.v.s, $3);
      }
    ;

class_declaration
    : CLASS class_declarator_list SEMI
      {
        objc_str_list_t strs;
        char *repl;

        strs = $2->a.strs;
        while (strs) {
          st_insert(objc_declared_class_table, strs->car, strs->car);
          strs = strs->cdr;
        }
        $$ = objc_make_cls_decl_node($2);

        /* patch */
        repl = ALLOC_ATOM_N(char, strlen($2->a.strs->car)+24);
        sprintf(repl, "typedef struct %s ", $2->a.strs->car);
        objc_add_patch($1.lc, $1.v.len, repl);
      }
    ;

class_declarator_list
    : IDENTIFIER
      {
        $$ = objc_make_cls_dclr_list_node($1.v.s);
      }
    | class_declarator_list COMMA IDENTIFIER
      {
        char *repl;

        objc_str_list_add_set(&$1->a.strs, $3.v.s);
        $$ = $1;

        repl = ALLOC_ATOM_N(char, strlen($3.v.s)+24);
        sprintf(repl, "; typedef struct %s ", $3.v.s);
        objc_add_patch($2, 1, repl);
      }
    ;

class_interface
    : class_interface_specifier class_specifier END
      {
        char *defs;

        $$ = objc_make_cls_intf_node($2, NULL);
        objc_current_class->is_abstract = objc_current_class_is_abstract;
        objc_current_class->is_sealed = objc_current_class_is_sealed;

        if (objc_current_class_is_declared)
          objc_add_patch($1.lc, $1.v.len, "struct ");
        else
          objc_add_patch($1.lc, $1.v.len, "typedef struct ");

        defs = get_all_ivar_defs(objc_current_class,
                                 objc_current_class_is_declared);
        objc_add_patch($3.lc, $3.v.len, defs);
      }
    | class_interface_specifier class_specifier class_interface_declaration END
      {
        char *repl, *defs;

        $$ = objc_make_cls_intf_node($2, $3);
        objc_current_class->is_abstract = objc_current_class_is_abstract;
        objc_current_class->is_sealed = objc_current_class_is_sealed;

        if ($3->a.cls_intf_decl.ivar_list != NULL)
          if (objc_current_class_is_declared)
            objc_add_patch($1.lc, $1.v.len, "struct ");
          else
            objc_add_patch($1.lc, $1.v.len, "typedef struct ");
        else {
          defs = get_all_ivar_defs(objc_current_class,
                                   objc_current_class_is_declared);
          repl = ALLOC_ATOM_N(char, strlen(objc_current_class->name) +
                              strlen(defs) + 24);
          sprintf(repl, "%sstruct %s %s", 
                  objc_current_class_is_declared ? "" : "typedef ",
                  objc_current_class->name, defs);
          objc_add_patch($1.lc, $1.v.len, repl);
          objc_add_patch(cls_name_lc, strlen(objc_current_class->name), NULL);
        }

        objc_add_patch($4.lc, $4.v.len, NULL);
      }
    ;

class_interface_specifier
    : INTERFACE
      {
        $$ = $1;
      }
    | ABSTRACT INTERFACE
      {
        $$ = $2;
        objc_current_class_is_abstract = YES;
        objc_current_class_is_sealed = NO;
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | SEALED INTERFACE
      {
        $$ = $2;
        objc_current_class_is_abstract = NO;
        objc_current_class_is_sealed = YES;
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    ;

class_specifier
    : class_name
      {
        cls_name_lc = $1.lc;
        $$ = objc_make_cls_spcr_node($1.v.s, NULL);
        objc_current_scope->cls = objc_current_class =
          objc_scope_set_class(objc_current_scope, $$);
        objc_current_class_is_declared = st_lookup(objc_declared_class_table, $1.v.s, NULL);
      }
    | class_name COLON CLASS_NAME
      {
        /* validate sealed */
        if (objc_get_class($3.v.s)->is_sealed)
          parse_error("'%s' is sealed", $3.v.s);

        cls_name_lc = $1.lc;
        $$ = objc_make_cls_spcr_node($1.v.s, $3.v.s);
        objc_current_scope->cls = objc_current_class =
          objc_scope_set_class(objc_current_scope, $$);
        objc_current_class_is_declared = st_lookup(objc_declared_class_table, $1.v.s, NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    | class_name COLON IDENTIFIER
      {
        parse_error("cannot find interface declaration for `%s', "
                    "superclass of `%s'", $3.v.s, $1.v.s);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    | class_name COLON DECL_CLASS_NAME
      {
        parse_error("cannot find interface declaration for `%s', "
                    "superclass of `%s'", $3.v.s, $1.v.s);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    ;

class_name
    : IDENTIFIER        { $$ = $1; }
    | DECL_CLASS_NAME   { $$ = $1; }
    ;

class_interface_declaration
    : protocol_reference_list
      {
        $$ = objc_make_cls_intf_decl_node($1, NULL, NULL);
      }
    | protocol_reference_list instance_variable_list
      {
        $$ = objc_make_cls_intf_decl_node($1, $2, NULL);
      }
    | protocol_reference_list instance_variable_list interface_declaration_list
      {
        $$ = objc_make_cls_intf_decl_node($1, $2, $3);
      }
    | protocol_reference_list interface_declaration_list
      {
        $$ = objc_make_cls_intf_decl_node($1, NULL, $2);
      }
    | instance_variable_list
      {
        $$ = objc_make_cls_intf_decl_node(NULL, $1, NULL);
      }
    | instance_variable_list interface_declaration_list
      {
        $$ = objc_make_cls_intf_decl_node(NULL, $1, $2);
      }
    | interface_declaration_list
      {
        $$ = objc_make_cls_intf_decl_node(NULL, NULL, $1);
      }
    ;

protocol_reference_list
    : LT_OP protocol_list GT_OP
      { 
        $$ = $2;
        objc_add_patch($1, 1, NULL);
        objc_add_patch($3, 1, NULL);
      }
    ;

protocol_list
    : protocol_reference_name
      {
        objc_protocol_t prtc;

        $$ = objc_make_prtc_list_node($1);
        objc_nimpl_protocols++;

        prtc = objc_get_protocol($1);
        if (prtc)
          if (objc_current_class)
            objc_protocol_list_add_set(&objc_current_class->protocols, prtc);
          else
            objc_protocol_list_add_set(&current_protocol->protocols, prtc);
        else
          parse_error("protocol `%s' is not declared", $1);
      }
    | protocol_list COMMA protocol_reference_name
      { 
        objc_protocol_t prtc;

        objc_node_add_str($1, $3);
        $$ = $1;
        objc_add_patch($2, 1, NULL);
        objc_nimpl_protocols++;

        prtc = objc_get_protocol($3);
        if (prtc)
          if (objc_current_class)
            objc_protocol_list_add_set(&objc_current_class->protocols, prtc);
          else
            objc_protocol_list_add_set(&current_protocol->protocols, prtc);
        else
          parse_error("protocol `%s' is not declared", $3);
      }
    ;

protocol_reference_name
    : IDENTIFIER
      {
        $$ = $1.v.s;
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    | CLASS_NAME
      {
        $$ = $1.v.s;
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    | DECL_CLASS_NAME
      {
        $$ = $1.v.s;
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    ;

instance_variable_list
    : LBRACE RBRACE
      {
        if (objc_current_class->superclass)
          add_superclass_ivar_patch($1, objc_current_class->superclass);
        add_class_struct_patch($2);
        $$ = objc_make_ivar_decl_list_node(NULL);
      }
    | LBRACE instance_variable_declaration_list RBRACE
      {
        int i, j, vsbl = OBJC_PUBLIC;
        objc_node_t ivar_decl, dclr_list, spcr_qual_list, dclr;
        objc_ivar_t ivar;
        size_t len;
        char *buf;

        /* generate instance variable representation for subclasses */
        len = $3 - $1;
        if (objc_current_class->superclass) {
          add_superclass_ivar_patch($1, objc_current_class->superclass);
          buf = objc_current_class->ivars_repr = ALLOC_ATOM_N(char,
            len + strlen(objc_current_class->superclass->ivars_repr) + 1);
          buf += sprintf(buf, "%s", objc_current_class->superclass->ivars_repr);
        } else
          buf = objc_current_class->ivars_repr = ALLOC_ATOM_N(char, len + 1);
        fseek(parsing_file, $1+1, SEEK_SET);
        fread(buf, len-1, 1, parsing_file);
        buf[len-1] = 0;
        buf = objc_current_class->ivars_repr;
        while (*buf) {
          switch (*buf) {
          case '\r':
          case '\n':
            *buf = ' ';
          }
          buf++;
        }

        /* create instance variable objects */
        for (i = 0; i < $2->a.l.nsubs; i++) {
          ivar_decl = $2->a.l.subs[i];
          if (ivar_decl->type == OBJC_VSBL_SPEC_NODE)
            vsbl = ivar_decl->a.ival;
          if (ivar_decl->type == OBJC_STRUCT_DECL_NODE) {
            dclr_list = ivar_decl->a.struct_decl.dclr_list;
            for (j = 0; j < dclr_list->a.l.nsubs; j++) {
              spcr_qual_list = ivar_decl->a.struct_decl.spcr_qual_list;
              dclr = dclr_list->a.l.subs[j];
              ivar = ALLOC(struct objc_ivar);
              ivar->node = objc_make_ivar_decl_node(spcr_qual_list, dclr);
              ivar->vsbl = vsbl;
              ivar->name = objc_node_dclr_name(dclr);
              ivar->type_name = objc_generate_type_name(ivar->node, NO);
              ivar->ctype = NULL;
              ivar->enc = objc_make_type_encoding_from_node(ivar->node);
              objc_ivar_list_add_set(&objc_current_class->ivars, ivar);
              objc_nimpl_ivars++;
            }
          }
        }

        add_class_struct_patch($3);
        $$ = $2;
      }
    ;

instance_variable_declaration_list
    : instance_variable_declaration
      { 
        $$ = objc_make_ivar_decl_list_node($1);
      }
    | instance_variable_declaration_list instance_variable_declaration
      { 
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

instance_variable_declaration
    : struct_declaration        { $$ = $1; }
    | visibility_specification  { $$ = $1; }
    ;

visibility_specification
    : PRIVATE
      {
        $$ = objc_make_vsbl_spec_node(OBJC_PRIVATE);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | PROTECTED
      {
        $$ = objc_make_vsbl_spec_node(OBJC_PROTECTED);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | PUBLIC
      {
        $$ = objc_make_vsbl_spec_node(OBJC_PUBLIC);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    ;

interface_declaration_list
    : interface_declaration
      {
        if ($1)
          $$ = objc_make_intf_decl_list_node($1);
      }
    | interface_declaration_list interface_declaration
      {
        if ($2) {
          if ($1) {
            objc_node_add($1, $2);
            $$ = $1;
          } else
            $$ = objc_make_intf_decl_list_node($2);
        } else if ($1)
          $$ = $1;
        else
          $$ = NULL;
      }
    ;

interface_declaration
    : declaration           { $$ = $1; }
    | method_declaration    { $$ = $1; }
    | OPTIONAL
      {
        $$ = NULL;
        objc_add_patch($1.lc, $1.v.len, NULL);
        if (current_protocol) {
          protocol_opt_decls = NO;
        } else
          parse_error("cannot use @optional in the context");
      }
    | REQUIRED
      {
        $$ = NULL;
        objc_add_patch($1.lc, $1.v.len, NULL);
        if (current_protocol) {
          protocol_opt_decls = YES;
        } else
          parse_error("cannot use @required in the context");
      }
    ;

method_declaration
    : method_declarator SEMI
      {
        objc_node_t spcr, quals, qual;
        objc_method_t method, supermeth;
        objc_method_sig_t sig;
        objc_protocol_method_t pmeth;
        int i;
        BOOL is_inst;

        $$ = objc_make_mth_decl_node($1);

        if (objc_current_class) {
          spcr = $1->a.mth_dclr.mth_spcr;
          is_inst = spcr->a.mth_spcr.rtype;
          sig = register_method_sig(objc_current_class, method_name, $1, is_inst);
          if (sig) {
            method = ALLOC(struct objc_method);
            method->owner = objc_current_class;
            method->is_inst = spcr->a.mth_spcr.rtype;
            method->has_impl = NO;
            method->node = $1;
            method->sig = sig;
            method->vsbl = OBJC_PUBLIC;
            method->is_abstract = NO;
            method->is_sealed = NO;
            method->imp = objc_make_imp_name(spcr->a.mth_spcr.rtype,
                                             objc_current_class->name,
                                             method_name);

            quals = $1->a.mth_dclr.mth_qual_list;
            if (quals) {
              for (i = 0; i < quals->a.l.nsubs; i++) {
                qual = quals->a.l.subs[i];
                if (qual->a.tag == PUBLIC_QUAL)
                  method->vsbl = OBJC_PUBLIC;
                else if (qual->a.tag == PROTECTED_QUAL)
                  method->vsbl = OBJC_PROTECTED;
                else if (qual->a.tag == PRIVATE_QUAL)
                  method->vsbl = OBJC_PRIVATE;
                else if (qual->a.tag == ABSTRACT)
                  method->is_abstract = YES;
                else if (qual->a.tag == SEALED)
                  method->is_sealed = YES;
              }
            }

            /* validate private and sealed */
            if (objc_current_class->superclass) {
              supermeth = objc_class_get_method(objc_current_class->superclass,
                                                method_name,
                                                spcr->a.mth_spcr.rtype);
              if (supermeth) {
                if (supermeth->vsbl == OBJC_PRIVATE)
                  parse_error("cannot override private method '%s'", method_name);
                if (supermeth->is_sealed)
                  parse_error("cannot override sealed method '%s'", method_name);
              }
            }

            if (spcr->a.mth_spcr.rtype)
              objc_method_list_add_set(&objc_current_class->inst_meths, method);
            else
              objc_method_list_add_set(&objc_current_class->class_meths, method);
          }
        } else if (current_protocol) {
          spcr = $1->a.mth_dclr.mth_spcr;
          sig = register_protocol_method_sig(current_protocol, method_name, $1, spcr->a.mth_spcr.rtype);
          if (sig) {
            pmeth = objc_make_protocol_method(sig, protocol_opt_decls,
                                              spcr->a.mth_spcr.rtype);
            objc_protocol_add_method(current_protocol, pmeth,
                                     spcr->a.mth_spcr.rtype);
            if (spcr->a.mth_spcr.rtype)
              objc_ninst_meth_descs++;
            else
              objc_nclass_meth_descs++;
          }
        }
        method_name = NULL;
      }
    ;

method_declarator
    : method_qualifier_list method_specifier method_selector
      {
        $$ = objc_make_mth_dclr_node($1, $2, $3);
        ret_ctype = objc_ctype_from_decl(objc_current_scope,
                                         $2->a.mth_spcr.mtype, NULL);
      }
    | method_specifier method_selector
      {
        $$ = objc_make_mth_dclr_node(NULL, $1, $2);
        ret_ctype = objc_ctype_from_decl(objc_current_scope,
                                         $1->a.mth_spcr.mtype, NULL);
      }
    ;

method_qualifier_list 
    : method_qualifier
      {
        $$ = objc_make_mth_qual_list_node($1);
      }
    | method_qualifier_list method_qualifier
      {
        $$ = $1;
        objc_node_add($1, $2);
      }
    ;

method_qualifier
    : PUBLIC_QUAL
      {
        $$ = objc_make_mth_qual_node(PUBLIC_QUAL);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | PROTECTED_QUAL
      {
        $$ = objc_make_mth_qual_node(PROTECTED_QUAL);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | PRIVATE_QUAL
      {
        $$ = objc_make_mth_qual_node(PRIVATE_QUAL);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | ABSTRACT
      {
        $$ = objc_make_mth_qual_node(ABSTRACT);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | SEALED
      {
        $$ = objc_make_mth_qual_node(SEALED);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    ;

method_specifier
    : receiver_type
      {
        char *type, *name;

        $$ = objc_make_mth_spcr_node($1, NULL);

        in_mret_type = NO;
        mtype_is_omitted = YES;
        name = get_interface_name();
        type = ALLOC_ATOM_N(char, strlen(name)+2);
        sprintf(type, "%s*", name);
        objc_add_patch(recv_type_op_lc, 1, type);
      }
    | receiver_type method_type
      {
        $$ = objc_make_mth_spcr_node($1, $2);
        objc_add_patch(recv_type_op_lc, 1, NULL);
        in_mret_type = NO;
        mtype_is_omitted = NO;
      }
    ;

receiver_type
    : PLUS_OP
      {
        $$ = 0;
        is_inst_meth = NO;
        kw_dclr_colon_lc = 0;
        in_mret_type = YES;
        recv_type_op_lc = $1;
      }
    | MINUS_OP
      {
        $$ = 1;
        is_inst_meth = YES;
        kw_dclr_colon_lc = 0;
        in_mret_type = YES;
        recv_type_op_lc = $1;
      }
    ;

class_implementation
    : IMPL class_implementation_specifier END
      {
        $$ = objc_make_cls_impl_node($2, NULL);
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($3.lc, $3.v.len, NULL);
        validate_all_meth_defs();
        objc_nimpl_classes++;
      }
    | IMPL class_implementation_specifier implementation_definition_list END
      {
        $$ = objc_make_cls_impl_node($2, $3);
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($4.lc, $4.v.len, NULL);
        validate_all_meth_defs();
        objc_nimpl_classes++;
      }
    ;

class_implementation_specifier
    : CLASS_NAME
      {
        $$ = objc_make_cls_spcr_node($1.v.s, NULL);
        objc_current_scope->cls = objc_current_class = objc_get_class($1.v.s);
        if (objc_current_class == NULL)
          parse_error("%s: undeclared", $1.v.s);

        objc_current_class->has_impl = 1;
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    | CLASS_NAME COLON CLASS_NAME
      {
        $$ = objc_make_cls_spcr_node($1.v.s, $3.v.s);
        objc_current_scope->cls = objc_current_class = objc_get_class($1.v.s);
        if (objc_current_class == NULL)
          parse_error("%s: undeclared", $1.v.s);

        objc_current_class->has_impl = 1;
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    | IDENTIFIER
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    | IDENTIFIER COLON IDENTIFIER
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        parse_error("cannot find interface declaration for `%s'", $3.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    | IDENTIFIER COLON CLASS_NAME
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    | IDENTIFIER COLON DECL_CLASS_NAME
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        parse_error("cannot find interface declaration for `%s'", $3.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    | DECL_CLASS_NAME
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    | DECL_CLASS_NAME COLON IDENTIFIER
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        parse_error("cannot find interface declaration for `%s'", $3.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    | DECL_CLASS_NAME COLON CLASS_NAME
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    | DECL_CLASS_NAME COLON DECL_CLASS_NAME
      {
        parse_error("cannot find interface declaration for `%s'", $1.v.s);
        parse_error("cannot find interface declaration for `%s'", $3.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    ;

implementation_definition_list
    : implementation_definition
      {
        if ($1 != NULL)
          $$ = objc_make_impl_def_list_node($1);
      }
    | implementation_definition_list implementation_definition
      {
        if ($1 != NULL && $2 != NULL) {
          objc_node_add($1, $2);
          $$ = $1;
        } else if ($1 != NULL)
          $$ = $1;
        else if ($2 != NULL)
          $$ = objc_make_impl_def_list_node($2);
        else
          $$ = NULL;
      }
    ;

implementation_definition
    : function_definition   { $$ = NULL; }
    | declaration           { $$ = NULL; }
    | method_definition
      {
        char *name;
        objc_method_t meth;
        BOOL is_inst;

        is_meth_def_scope = NO;
        $$ = $1;
        is_inst = $$->a.mth_def.mth_dclr->a.mth_dclr.mth_spcr->a.mth_spcr.rtype;
        name = objc_node_get_method_name($$);
        meth = objc_class_get_method(objc_current_class, name, is_inst);
        meth->has_impl = YES;

        if (is_inst)
          objc_nimpl_inst_meths++;
        else
          objc_nimpl_class_meths++;
        method_name = NULL;

        if (mtype_is_omitted)
          objc_add_patch(last_compound_stmt_brace_lc, 1, " return self;}");
      }
    ;

method_definition
    : method_declarator
      {
        is_meth_def_scope = YES;
        kw_meth_first_brace = YES;
        func_args_node = $1->a.mth_dclr.mth_sel;
      }
      compound_statement
      {
        $$ = objc_make_mth_def_node($1, NULL, $3);
        is_meth_def_scope = NO;
      }
    | method_declarator
      {
        is_meth_def_scope = YES;
        kw_meth_first_brace = YES;
        func_args_node = $1->a.mth_dclr.mth_sel;
      }
      declaration_list compound_statement
      {
        $$ = objc_make_mth_def_node($1, $3, $4);
        is_meth_def_scope = NO;
      }
    ;

method_selector
    : IDENTIFIER
      {
#define FORMAT "%s(%s *self " ATTR_UNUSED ", " "SEL _cmd " ATTR_UNUSED ")"
        char *repl, *imp, *name;

        method_name = $1.v.s;
        $$ = objc_make_mth_unary_sel_node($1.v.s);

        /* patch */
        name = get_interface_name();
        imp = objc_make_imp_name(is_inst_meth, name, method_name);
        repl = ALLOC_ATOM_N(char, strlen(imp) +
                            strlen(name) + strlen(FORMAT) + 1);
        sprintf(repl, FORMAT, imp, name);
        objc_add_patch($1.lc, strlen($1.v.s), repl);
#undef FORMAT
      }
    | keyword_selector
      {
        char *name;

        method_name = generate_kw_sel($1);
        $$ = objc_make_mth_kw_sel_node($1, method_name, 0);
        name = get_interface_name();
        add_kw_sel_patches(is_inst_meth, kw_dclr_colon_lc,
                           name, method_name);
        objc_add_patch(kw_msg_dclr_close_lc, 0, ")");
      }
    | keyword_selector COMMA ELLIPSIS
      {
        char *name;

        method_name = generate_kw_sel($1);
        $$ = objc_make_mth_kw_sel_node($1, method_name, 1);
        name = get_interface_name();
        add_kw_sel_patches(is_inst_meth, kw_dclr_colon_lc,
                           name, method_name);
        objc_add_patch($3, 3, "...)");
      }
    ;

keyword_selector
    : keyword_declarator
      {
        $$ = objc_make_kw_sel_node($1);
      }
    | keyword_selector keyword_declarator
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

keyword_declarator
    : COLON IDENTIFIER
      {
        $$ = objc_make_kw_dclr_node(NULL, NULL, $2.v.s);
        if (kw_dclr_colon_lc == 0)
          kw_dclr_colon_lc = $1;
        else
          objc_add_patch($1, 1, ", id ");
        kw_msg_dclr_close_lc = $2.lc + strlen($2.v.s);
      }
    | COLON method_type IDENTIFIER
      {
        $$ = objc_make_kw_dclr_node(NULL, $2, $3.v.s);
        if (kw_dclr_colon_lc == 0)
          kw_dclr_colon_lc = $1;
        else
          objc_add_patch($1, 1, ", ");
        kw_msg_dclr_close_lc = $3.lc + strlen($3.v.s);
      }
    | IDENTIFIER COLON IDENTIFIER
      {
        $$ = objc_make_kw_dclr_node($1.v.s, NULL, $3.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        if (kw_dclr_colon_lc == 0)
          kw_dclr_colon_lc = $2;
        else
          objc_add_patch($2, 1, ", id ");
        kw_msg_dclr_close_lc = $3.lc + strlen($3.v.s);
      }
    | IDENTIFIER COLON method_type IDENTIFIER
      {
        $$ = objc_make_kw_dclr_node($1.v.s, $3, $4.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        if (kw_dclr_colon_lc == 0)
          kw_dclr_colon_lc = $2;
        else
          objc_add_patch($2, 1, ", ");
        kw_msg_dclr_close_lc = $4.lc + strlen($4.v.s);
      }
    ;

method_type
    : LPAREN type_name RPAREN
      {
        $$ = $2;
        objc_add_patch($1, 1, NULL);
        objc_add_patch($3, 1, " ");
      }
    ;

category_interface
    : INTERFACE category_specifier END
      {
        $$ = objc_make_cat_intf_node($2, NULL);
      }
    | INTERFACE category_specifier category_interface_declaration_list END
      {
        $$ = objc_make_cat_intf_node($2, $3);
      }
    ;

category_specifier
    : CLASS_NAME LPAREN category_name RPAREN
      {
        $$ = objc_make_cat_spcr_node($1.v.s, $3.v.s);
      }
    ;

category_interface_declaration_list
    : protocol_reference_list
      {
        $$ = objc_make_cat_intf_decl_list_node($1, NULL);
      }
    | protocol_reference_list interface_declaration_list
      {
        $$ = objc_make_cat_intf_decl_list_node($1, $2);
      }
    | interface_declaration_list
      {
        $$ = objc_make_cat_intf_decl_list_node(NULL, $1);
      }
    ;

category_name
    : IDENTIFIER    { $$ = $1; }
    | CLASS_NAME    { $$ = $1; }
    ;

category_implementation
    : IMPL category_specifier END
      {
        $$ = objc_make_cat_impl_node($2, NULL);
      }
    | IMPL category_specifier implementation_definition_list END
      {
        $$ = objc_make_cat_impl_node($2, $3);
      }
    ;

protocol_declaration
    : PROTOCOL protocol_name END
      {
        $$ = objc_make_prtc_decl_node($2.v.s, NULL, NULL);
        current_protocol->has_def = YES;
        objc_nprotocols++;
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($3.lc, $3.v.len, NULL);
      }
    | PROTOCOL protocol_name protocol_reference_list END
      {
        $$ = objc_make_prtc_decl_node($2.v.s, $3, NULL);
        current_protocol->has_def = YES;
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($4.lc, $4.v.len, NULL);
      }
    | PROTOCOL protocol_name protocol_reference_list interface_declaration_list END
      {
        $$ = objc_make_prtc_decl_node($2.v.s, $3, $4);
        current_protocol->has_def = YES;
        objc_nprotocols++;
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($5.lc, $5.v.len, NULL);
      }
    | PROTOCOL protocol_name interface_declaration_list END
      {
        $$ = objc_make_prtc_decl_node($2.v.s, NULL, $3);
        current_protocol->has_def = YES;
        objc_nprotocols++;
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($4.lc, $4.v.len, NULL);
      }
    ;

protocol_name
    : IDENTIFIER
      {
        $$ = $1;
        parse_protocol_name($1.lc, $1.v.s);
      }
    | CLASS_NAME
      {
        $$ = $1;
        parse_protocol_name($1.lc, $1.v.s);
      }
    ;

protocol_declaration_list
    : PROTOCOL protocol_name_list SEMI
      {
        objc_str_list_t names, temp;

        $$ = objc_make_prtc_decl_list_node($2);

        names = $2;
        while (names) {
          temp = names->cdr;
          while (temp) {
            if (STREQ(names->car, temp->car))
              parse_error("duplicate protocol declaration `%s'", names->car);
            temp = temp->cdr;
          }
          names = names->cdr;
        }

        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($3, 1, NULL);
      }
    ;

protocol_name_list 
    : protocol_name
      {
        $$ = objc_make_str_list($1.v.s, NULL);
        objc_declare_protocol($1.v.s);
      }
    | protocol_name_list COMMA protocol_name
      {
        $$ = objc_str_list_add($$, $3.v.s);
        objc_declare_protocol($3.v.s);
        objc_add_patch($2, 1, NULL);
      }
    ;

compatibility_alias
    : COMPATIBILITY_ALIAS IDENTIFIER CLASS_NAME SEMI
      {
        $$ = NULL;
        objc_scope_set_alias_for_class(objc_current_scope, $2.v.s, $3.v.s);
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($2.lc, strlen($2.v.s), NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
        objc_add_patch($4, 1, NULL);
      }
    | COMPATIBILITY_ALIAS IDENTIFIER IDENTIFIER SEMI
      {
        $$ = NULL;
        parse_error("cannot find class '%s'", $2.v.s);
        parse_error("cannot find class '%s'", $3.v.s);
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($2.lc, strlen($2.v.s), NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
        objc_add_patch($4, 1, NULL);
      }
    | COMPATIBILITY_ALIAS CLASS_NAME IDENTIFIER SEMI
      {
        $$ = NULL;
        parse_error("class '%s' already exists", $2.v.s);
        parse_error("cannot find class '%s'", $3.v.s);
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($2.lc, strlen($2.v.s), NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
        objc_add_patch($4, 1, NULL);
      }
    | COMPATIBILITY_ALIAS CLASS_NAME CLASS_NAME SEMI
      {
        $$ = NULL;
        parse_error("class '%s' already exists", $2.v.s);
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($2.lc, strlen($2.v.s), NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
        objc_add_patch($4, 1, NULL);
      }
    ;

gcc_attribute_specifier
    : GCC_ATTR LPAREN LPAREN RPAREN RPAREN
      {
        $$ = objc_make_gcc_attr_spcr_node(NULL);
      }
    | GCC_ATTR LPAREN LPAREN gcc_direct_attribute_specifier_list RPAREN RPAREN
      {
        $$ = objc_make_gcc_attr_spcr_node($4);
      }
    ;

gcc_direct_attribute_specifier_list
    : gcc_direct_attribute_specifier
      {
        $$ = objc_make_gcc_dir_attr_spcr_list_node($1);
      }
    | gcc_direct_attribute_specifier_list COMMA gcc_direct_attribute_specifier
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

gcc_direct_attribute_specifier
    : gcc_attribute_name
      {
        $$ = objc_make_gcc_dir_attr_spcr_node($1, NULL);
      }
    | gcc_attribute_name constant_expression
      {
        $$ = objc_make_gcc_dir_attr_spcr_node($1, $2);
      }
    ;

gcc_attribute_name
    : IDENTIFIER    { $$ = $1.v.s; }
    | CONST         { $$ = objc_strdup("const"); }
    | CONST2        { $$ = objc_strdup("__const"); }
    | CONST3        { $$ = objc_strdup("__const__"); }
    ;

gcc_asm_statement
    : gcc_assembly SEMI
      {
        $$ = objc_make_gcc_asm_stmt_node($1);;
      }
    ;

gcc_assembly
    : gcc_asm LPAREN STR_LIT RPAREN
      {
        $$ = objc_make_gcc_asm_node($1, NULL, $3.v.s, NULL);
      }
    | gcc_asm gcc_asm_volatile LPAREN STR_LIT RPAREN
      {
        $$ = objc_make_gcc_asm_node($1, $2, $4.v.s, NULL);
      }
    | gcc_asm LPAREN STR_LIT COLON gcc_asm_option_list RPAREN
      {
        $$ = objc_make_gcc_asm_node($1, NULL, $3.v.s, $5);
      }
    | gcc_asm gcc_asm_volatile LPAREN STR_LIT COLON gcc_asm_option_list RPAREN
      {
        $$ = objc_make_gcc_asm_node($1, $2, $4.v.s, $6);
      }
    ;

gcc_asm
    : GCC_ASM   { $$ = "asm"; }
    | GCC_ASM2  { $$ = "__asm"; }
    | GCC_ASM3  { $$ = "__asm__"; }
    ;

gcc_asm_volatile
    : VOLATILE          { $$ = "volatile"; }
    | VOLATILE2         { $$ = "__volatile"; }
    | VOLATILE3         { $$ = "__volatile__"; }
    ;

gcc_asm_option_list
    : gcc_asm_operand_list
      {
        $$ = objc_make_gcc_asm_opt_list_node($1, NULL, NULL);
      }
    | gcc_asm_operand_list COLON gcc_asm_operand_list
      {
        $$ = objc_make_gcc_asm_opt_list_node($1, $3, NULL);
      }
    | gcc_asm_operand_list COLON gcc_asm_operand_list COLON gcc_asm_clobber_list
      {
        $$ = objc_make_gcc_asm_opt_list_node($1, $3, $5);
      }
    ;

gcc_asm_operand_list
    : gcc_asm_operand
      {
        $$ = objc_make_gcc_asm_op_list_node($1);
      }
    | gcc_asm_operand_list COMMA gcc_asm_operand
      {    
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

gcc_asm_operand
    : STR_LIT LPAREN expression RPAREN
      {
        $$ = objc_make_gcc_asm_op_node($1.v.s, $3);
      }
    ;

gcc_asm_clobber_list
    : STR_LIT
      {
        $$ = objc_make_gcc_asm_clob_list_node($1.v.s);
      }
    | gcc_asm_clobber_list COMMA STR_LIT
      {
        $1->a.strs = objc_str_list_add($1->a.strs, $3.v.s);
        $$ = $1;
      }
    ;

%%

static void
begin_scope()
{
  if (is_meth_def_scope)
    objc_current_scope = objc_make_meth_def_scope(objc_current_scope);
  else
    objc_current_scope = objc_make_scope(objc_current_scope);

  if (func_args_node) {
    set_func_args(func_args_node);
    func_args_node = NULL;
  }
}

static void
end_scope()
{
  objc_current_scope = objc_current_scope->parent;
}

static void
set_func(objc_node_t decl_spcr_list,
         objc_node_t dclr,
         objc_node_t decl_list)
{
  char *func_ctype;

  if (dclr->a.dclr.base_dclr->a.base_dclr.dir_dclr->type !=
      OBJC_FUNC_DIR_DCLR_NODE)
    parse_error("syntax error");
  func_args_node = objc_make_func_def_node(decl_spcr_list, dclr, decl_list, NULL);
  objc_scope_set_var(objc_current_scope, func_args_node, dclr,
                     objc_node_get_ident_name(dclr));

  /* ret_dclr = decl_spcr_list + dclr */
  func_ctype = objc_ctype_from_decl(objc_current_scope, func_args_node, NULL);
  ret_ctype = objc_ctype_apply_func(func_ctype);
}

static void
validate_type_spcrs_and_type_quals(objc_node_t node)
{
  unsigned int storage_cls_spcr_flag = 0,
    type_qual_flag = 0, type_spcr_flag = 0;
  int i;
  objc_node_t sub;
  BOOL has_prtcs = NO;

  for (i = 0; i < node->a.l.nsubs; i++) {
    sub = node->a.l.subs[i];
    switch (sub->type) {
    case OBJC_PRTC_LIST_NODE:
      has_prtcs = YES;
      break;
    case OBJC_TYPE_QUAL_NODE:
      validate_type_qual(sub->a.tag, &type_qual_flag);
      break;
    case OBJC_TYPE_SPCR_NODE:
      validate_type_spcr(sub->a.tag, &type_spcr_flag);
      break;
    case OBJC_STORAGE_CLS_SPCR_NODE:
      validate_storage_cls_spcr(sub->a.tag, &storage_cls_spcr_flag);
      break;
    }
  }

  /* protocol */
  if (type_spcr_flag && has_prtcs)
    parse_error("non-object cannot confirm protocol");

  /* type specifier conflict */
  if ((type_spcr_flag & TYPE_SPCR_SIGNED) &&
      (type_spcr_flag & TYPE_SPCR_UNSIGNED))
    parse_error("both `signed' and `unsigned' in declaration specifiers");

  if ((type_spcr_flag & TYPE_SPCR_CHAR) &&
      (type_spcr_flag & TYPE_SPCR_SHORT))
    parse_error("both `char' and `short' in declaration specifiers");

  if ((type_spcr_flag & TYPE_SPCR_SHORT) &&
      (type_spcr_flag & TYPE_SPCR_LONG))
    parse_error("both `short' and `long' in declaration specifiers");
  
  if ((type_spcr_flag & TYPE_SPCR_FLOAT) &&
      (type_spcr_flag & TYPE_SPCR_DOUBLE))
    parse_error("both `float' and `double' in declaration specifiers");

  if (type_spcr_flag & TYPE_SPCR_SIGNED) {
    if (type_spcr_flag & TYPE_SPCR_FLOAT)
      parse_error("both `signed' and `float' in declaration specifiers");
    else if (type_spcr_flag & TYPE_SPCR_DOUBLE)
      parse_error("both `signed' and `double' in declaration specifiers");
  } else if (type_spcr_flag & TYPE_SPCR_UNSIGNED) {
    if (type_spcr_flag & TYPE_SPCR_FLOAT)
      parse_error("both `unsigned' and `float' in declaration specifiers");
    else if (type_spcr_flag & TYPE_SPCR_DOUBLE)
      parse_error("both `unsigned' and `double' in declaration specifiers");
  }
}

static void
validate_type_spcr(int tag, unsigned int *type_spcr_flagp)
{
  unsigned int type_spcr_flag, flag;

  type_spcr_flag = *type_spcr_flagp;
  flag = type_spcr_tag_to_flag(tag);

  if (type_spcr_flag == 0)
    type_spcr_flag |= flag;
  else if (tag == CHAR && (type_spcr_flag & TYPE_SPCR_CHAR))
    parse_error("duplicate 'char'");
  else if (tag == SHORT && (type_spcr_flag & TYPE_SPCR_SHORT))
    parse_error("duplicate 'short'");
  else if (tag == INT && (type_spcr_flag & TYPE_SPCR_INT))
    parse_error("duplicate 'int'");
  else if (tag == LONG) {
#ifdef HAVE_LONG_LONG
    if (type_spcr_flag & TYPE_SPCR_LONG_LONG)
      parse_error("`long long long' is too long");
    else if (type_spcr_flag & TYPE_SPCR_LONG)
      type_spcr_flag |= TYPE_SPCR_LONG_LONG;
#else
    if (type_spcr_flag & TYPE_SPCR_LONG)
      parse_error("duplicate `long'");
#endif /* HAVE_LONG_LONG */
  } else if (tag == FLOAT && (type_spcr_flag & TYPE_SPCR_FLOAT))
    parse_error("duplicate 'float'");
  else if (tag == DOUBLE && (type_spcr_flag & TYPE_SPCR_DOUBLE))
    parse_error("duplicate 'double'");
  else if ((tag == SIGNED || tag == SIGNED2 || tag == SIGNED3) &&
           (type_spcr_flag & TYPE_SPCR_SIGNED))
    parse_error("duplicate 'signed'");
  else if (tag == UNSIGNED && (type_spcr_flag & TYPE_SPCR_UNSIGNED))
    parse_error("duplicate 'unsigned'");
  else if (tag == VOID && (type_spcr_flag & TYPE_SPCR_VOID))
    parse_error("duplicate 'void'");
  else
    type_spcr_flag |= flag;
  *type_spcr_flagp = type_spcr_flag;
}

static unsigned int
type_spcr_tag_to_flag(int tag)
{
  switch (tag) {
  case CHAR:        return TYPE_SPCR_CHAR;
  case SHORT:       return TYPE_SPCR_SHORT;
  case INT:         return TYPE_SPCR_INT;
  case LONG:        return TYPE_SPCR_LONG;
  case FLOAT:       return TYPE_SPCR_FLOAT;
  case DOUBLE:      return TYPE_SPCR_DOUBLE;
  case VOID:        return TYPE_SPCR_VOID;
  case UNSIGNED:    return TYPE_SPCR_UNSIGNED;
  case SIGNED: case SIGNED2: case SIGNED3:
    return TYPE_SPCR_SIGNED;
  default:
    return 0;
  }
}

static void
validate_type_qual(int tag, unsigned int *type_qual_flag)
{
}

static void
validate_storage_cls_spcr(int tag, unsigned int *storage_cls_spcr_flag)
{
  if (*storage_cls_spcr_flag == 0)
    *storage_cls_spcr_flag |= tag;
  else
    parse_error("multiple storage classes in declaration specifiers");
}

static char *
generate_kw_sel(objc_node_t node)
{
  objc_str_stream_t s;
  char *name;
  int i;

  s = objc_make_str_stream();
  for (i = 0; i < node->a.l.nsubs; i++) {
    objc_str_stream_write(s, node->a.l.subs[i]->a.kw_dclr.name);
    objc_str_stream_write(s, ":");
  }
  name = objc_str_stream_copy_value(s);

  return name;
}

static char *
get_interface_name()
{
  if (objc_current_class)
    return objc_current_class->name;
  else if (current_protocol)
    return current_protocol->name;
  else {
    parse_error("not in class and protocol declaration");
    return NULL;
  }
}

static void
parse_protocol_name(size_t lc, char *name)
{
  objc_protocol_t prtc;
  char *repl;

  prtc = objc_get_protocol(name);
  if (prtc && prtc->has_def)
    objc_print_error("Protocol `%s' is already declared", name);
  else {
    if (prtc) {
      current_protocol = prtc;
      objc_add_patch(lc, strlen(name), NULL);
      return;
    } else {
      current_protocol = objc_make_protocol(name);
      objc_register_protocol(current_protocol);
    }
    protocol_opt_decls = NO;
  }
  repl = ALLOC_ATOM_N(char, strlen(PROTOCOL_DEFINE_FORMAT)+strlen(name)+1);
  sprintf(repl, PROTOCOL_DEFINE_FORMAT, name);
  objc_add_patch(lc, strlen(name), repl);
}

void
yyerror(const char *s)
{
  fflush(stdout);
  printf("%s:%d:%d: %s\n",
         objc_current_line_mark->file,
         objc_current_line_mark->lineno,
         objc_parsing_column, s);
}

static void
parse_error(const char *s, ...)
{
  va_list ap;

  va_start(ap, s);
  parse_logv("error", s, ap);
  va_end(ap);
  has_error = YES;
  exit(1);
}

static void
parse_warn(const char *s, ...)
{
  va_list ap;

  va_start(ap, s);
  parse_logv("warning", s, ap);
  va_end(ap);
}

static void
parse_logv(const char *tag, const char *s, va_list ap)
{
  fflush(stdout);
  printf("%s:%d: %s: ",
         objc_current_line_mark->file,
         objc_current_line_mark->lineno, tag);
  vprintf(s, ap);
  printf("\n");
}

int
objc_parse_file(const char *path)
{
  int st;

#if YYDEBUG != 0
  yydebug = 1;
#endif

  parsing_path              = path;
  has_error                 = NO;
  found_typedef             = 0;
  method_name               = NULL;
  is_inst_meth              = NO;
  is_meth_def_scope         = NO;
  gen_sel_table             = NO;
  cls_name_lc               = 0;
  sel_table_lc              = 0;
  ic_table_lc               = 0;
  objc_str_table_lc         = 0;
  kw_dclr_colon_lc          = 0;
  kw_msg_dclr_close_lc      = 0;
  kw_meth_first_brace       = NO;
  recv_info                 = NULL;
  ret_ctype                 = NULL;
  func_args_node            = NULL;
  objc_objc_strs            = NULL;
  objc_all_objc_strs_len    = 0;

  objc_added_nlines         = 0;
  objc_nobjc_strs           = 0;
  objc_in_decl_spcr_list    = 0;
  objc_successes_decl_spcr  = 0;
  objc_nsend_sites     = 0;

  objc_root_scope = objc_current_scope = objc_make_root_scope();
  objc_objc_str_table = st_init_strtable();
  objc_declared_class_table = st_init_strtable();

  objc_init_scan();
  objc_init_class();
  objc_init_name();

  yyin = fopen(path, "r");
  if (yyin == NULL)
    objc_print_error("%s: No such file", path);
  parsing_file = fopen(path, "r");
  st = yyparse();
  fclose(yyin);
  fclose(parsing_file);
  add_rest_patches();

  return st || has_error;
}

static char *
get_all_ivar_defs(objc_class_t cls, BOOL declared)
{
  objc_str_stream_t st;
  objc_class_t temp;

  st = objc_make_str_stream(NULL);
  objc_str_stream_write(st, "{");
  if (cls->superclass) {
    temp = cls->superclass;
    while (temp) {
      if (temp->ivars_repr)
        objc_str_stream_write(st, temp->ivars_repr);
        temp = temp->superclass;
      }
    }

  if (declared)
    objc_str_stream_write(st, "};");
  else {
    objc_str_stream_write(st, "}");
    objc_str_stream_write(st, cls->name);
    objc_str_stream_write(st, ";");
  }

  return objc_str_stream_copy_value_and_free(st);
}

static void
add_superclass_ivar_patch(size_t lbrace, objc_class_t scls)
{
  char *patch;

  patch = ALLOC_ATOM_N(char, strlen(scls->ivars_repr) + 2);
  sprintf(patch, "{%s", scls->ivars_repr);
  objc_add_patch(lbrace, 1, patch);
}

static void
add_class_struct_patch(size_t lc)
{
  char *name;

  if (objc_current_class_is_declared)
    objc_add_patch(lc, 1, "};");
  else {
    name = ALLOC_ATOM_N(char, strlen(objc_current_class->name)+3);
    sprintf(name, "}%s;", objc_current_class->name);
    objc_add_patch(lc, 1, name);
  }
}

static void
add_ivar_ref_patch(size_t lc, const char *var)
{
  char *repl;

  if (objc_class_get_ivar(objc_current_class, var) != NULL) {
    repl = ALLOC_ATOM_N(char, strlen(var)+7);
    sprintf(repl, "self->%s", var);
    objc_add_patch(lc, strlen(var), repl);
  }
}

enum { COMPLETE, INCOMPLETE_WARN, INCOMPLETE_ERROR };

static void
validate_all_meth_defs()
{
#define FORMAT "incomplete implementation of class `%s'"
  int st = COMPLETE, temp;

  temp = validate_meth_defs(objc_current_class, YES, YES);
  if (st < temp) st = temp;
  temp = validate_meth_defs(objc_current_class, NO, YES);
  if (st < temp) st = temp;
  if (objc_current_class->protocols) {
    temp = validate_protocol_methods(objc_current_class,
                                     objc_current_class->protocols, YES);
    if (st < temp) st = temp;
  }

  switch (st) {
  case COMPLETE: break;
  case INCOMPLETE_WARN:
    parse_warn(FORMAT, objc_current_class->name);
    break;
  case INCOMPLETE_ERROR:
    parse_error(FORMAT, objc_current_class->name);
    break;
  }

  validate_meth_defs(objc_current_class, YES, NO);
  validate_meth_defs(objc_current_class, NO, NO);
  if (objc_current_class->protocols)
    validate_protocol_methods(objc_current_class,
                              objc_current_class->protocols, NO);
#undef FORMAT
}

static int
validate_meth_defs(objc_class_t cls, BOOL is_inst, BOOL check)
{
  objc_method_list_t meths, ms, sms;
  objc_class_t scls;
  BOOL abs_def;
  int st = COMPLETE;

  if (is_inst)
    meths = cls->inst_meths;
  else
    meths = cls->class_meths;

  /* validate implementation */
  ms = meths;
  while (ms) {
    if (!ms->car->has_impl) {
      st = INCOMPLETE_ERROR;
      if (!check)
        parse_error("'%c%s' is not implemented", is_inst ? '-' : '+',
                    ms->car->sig->name);
    }
    ms = ms->cdr;
  }

  /* validate abstract */
  scls = cls->superclass;
  while (scls) {
    if (is_inst)
      sms = scls->inst_meths;
    else
      sms = scls->class_meths;
    while (sms) {
      if (sms->car->is_abstract) {
        abs_def = NO;
        ms = meths;
        while (ms) {
          if (STREQ(sms->car->sig->name, ms->car->sig->name))
            abs_def = YES;
          ms = ms->cdr;
        }
        if (!abs_def) {
          st = INCOMPLETE_ERROR;
          if (!check)
            parse_error("abstract '%c%s' is not implemented", is_inst ? '-' : '+',
                        sms->car->sig->name);
        }
      }
      sms = sms->cdr;
    }
    scls = scls->superclass;
  }

  return st;
}

static int
validate_protocol_methods(objc_class_t cls,
                          objc_protocol_list_t prtcs,
                          BOOL check)
{
#define PROTOCOL_FORMAT "class `%s' does not fully implement the `%s' protocol"
  struct protocol_iter iter;
  objc_protocol_list_t subprtcs;
  st_table_t prtc_meths;
  int st = COMPLETE;
  BOOL valid;

  iter.cls = cls;
  iter.check = check;

  subprtcs = prtcs;
  while (subprtcs) {
    if (subprtcs->car->protocols)
      validate_protocol_methods(cls, subprtcs->car->protocols, check);
    subprtcs = subprtcs->cdr;
  }

  while (prtcs) {
    valid = YES;

    /* validate instance methods */
    iter.st = COMPLETE;
    prtc_meths = prtcs->car->inst_meths;
    st_foreach(prtc_meths, validate_protocol_method, &iter);
    if (iter.st > st)
      st = iter.st;
    if (iter.st != COMPLETE)
      valid = NO;

    /* validate class methods */
    iter.st = COMPLETE;
    prtc_meths = prtcs->car->class_meths;
    st_foreach(prtc_meths, validate_protocol_method, &iter);
    if (iter.st > st)
      st = iter.st;
    if (iter.st != COMPLETE)
      valid = NO;

    /* print warning or error */
    if (!valid && !check) {
      if (st == INCOMPLETE_WARN)
        parse_warn(PROTOCOL_FORMAT, cls->name, prtcs->car->name);
      else
        parse_error(PROTOCOL_FORMAT, cls->name, prtcs->car->name);
    }
    prtcs = prtcs->cdr;
  }
  return st;
#undef PROTOCOL_FORMAT
}

static int
validate_protocol_method(char *name,
                         objc_protocol_method_t prtc_meth,
                         struct protocol_iter *iter)
{
  objc_method_list_t meths;
  objc_method_sig_t sig;
  objc_class_t cls;

  cls = iter->cls;
  while (cls) {
    if (prtc_meth->is_inst)
      meths = cls->inst_meths;
    else
      meths = cls->class_meths;

    while (meths) {
      sig = meths->car->sig;
      if (STREQ(sig->name, name)) {
        if (objc_method_sig_compare_types_exact(sig, sig))
          return ST_CONTINUE;
        else {
          iter->st = INCOMPLETE_ERROR;
          if (!iter->check)
            parse_error("coflicting types for `%c%s'",
                        prtc_meth->is_inst ? '-' : '+', name);
        }
      }
      meths = meths->cdr;
    }
    cls = cls->superclass;
  }

  if (!prtc_meth->optional) {
    if (iter->st < INCOMPLETE_WARN)
      iter->st = INCOMPLETE_WARN;
    if (!iter->check)
      parse_warn("method definition for `%c%s' not found",
                 prtc_meth->is_inst ? '-' : '+', name);
  }

  return ST_CONTINUE;
}

static void
set_func_args(objc_node_t node)
{
  switch (node->type) {
  case OBJC_FUNC_DEF_NODE:
    {
      objc_node_t decl, dclr, params, param;
      int i, j;

      /* K&R */
      if (node->a.func_def.decl_list) {
        for (i = 0; i < node->a.func_def.decl_list->a.l.nsubs; i++) {
          decl = node->a.func_def.decl_list->a.l.subs[i];
          for (j = 0; j < decl->a.decl.init_dclr_list->a.l.nsubs; j++) {
            dclr = decl->a.decl.init_dclr_list->a.l.subs[j];
            objc_scope_set_var(objc_current_scope,
                               decl->a.decl.decl_spcr_list, dclr,
                               objc_node_get_ident_name(dclr));
          }
        }
      } else {
        params = node->a.func_def.dclr->a.dclr.base_dclr->
          a.base_dclr.dir_dclr->a.func_dir_dclr.param_list;
        if (params) {
          params = params->a.param_type_list.param_list;
          for (i = 0; i < params->a.l.nsubs; i++) {
            param = params->a.l.subs[i];
            dclr = param->a.param_decl.dclr;
            /* dclr == NULL when parameter list is "(void)" */
            if (dclr)
              objc_scope_set_var(objc_current_scope,
                                 param->a.param_decl.decl_spcr_list,
                                 dclr, objc_node_get_ident_name(dclr));
          }
        }
      }
    }
    break;

  case OBJC_MTH_UNARY_SEL_NODE:
    /* ignore */
    break;

  case OBJC_MTH_KW_SEL_NODE:
    {
      int i;
      objc_node_t kw_dclr;

      for (i = 0; i < node->a.mth_kw_sel.sel->a.l.nsubs; i++) {
        kw_dclr = node->a.mth_kw_sel.sel->a.l.subs[i];
        objc_scope_set_var(objc_current_scope,
                           kw_dclr->a.kw_dclr.mtype->a.type_name.spcr_qual_list,
                           kw_dclr->a.kw_dclr.mtype->a.type_name.abs_dclr,
                           kw_dclr->a.kw_dclr.arg);
      }
    }
    break;

  default:
    objc_print_error("set_func_args: not supported -- %s", objc_node_name(node));
  }
}

static void
add_kw_sel_patches(BOOL inst_meth, size_t colon_lc,
                   const char *cls, const char *meth)
{
#define FORMAT "%s(%s *self " ATTR_UNUSED ", SEL _cmd " ATTR_UNUSED ","
  char *repl, *imp;
  size_t i;

  imp = objc_make_imp_name(inst_meth, cls, meth);
  repl = ALLOC_N(char, strlen(imp) + strlen(cls) + strlen(FORMAT) + 1);
  sprintf(repl, FORMAT, imp, cls);
  for (i = 0; i < strlen(repl); i++) {
    if (repl[i] == ':')
      repl[i] = '_';
  }
  objc_add_patch(colon_lc, 1, repl);
#undef FORMAT
}

static void
add_lookup_imp_patches(size_t lb_lc, size_t rb_lc,
                       size_t kw_lc, size_t kw_len,
                       const char *meth_name,
                       objc_method_sig_t sig)
{
  char *repl, *buf;
  unsigned long sel_num;
  BOOL uses_dtable = NO, sealed = NO;
  int dcode = -1;
  const char *func;
  objc_method_t meth, m;
  objc_class_t cls;
  size_t bufsize;

  if (recv_info->cls) {
    meth = objc_class_get_method(recv_info->cls, meth_name, recv_info->is_inst);
    if (meth) {
      /* direct binding (private or sealed) */
      sealed = meth->is_sealed || meth->owner->is_sealed;
      if (sealed && meth->owner->superclass) {
        cls = meth->owner->superclass;
        while (cls) {
          m = objc_class_get_method(cls, meth_name, recv_info->is_inst);
          if (m && !m->is_abstract) {
            sealed = NO;
            break;
          }
          cls = cls->superclass;
        }
      } 
      if (meth->vsbl == OBJC_PRIVATE || sealed) {
        add_direct_binding_imp_patches(lb_lc, rb_lc, kw_lc, kw_len, meth);
        return;
      }
    }
  }

  objc_add_patch(lb_lc, 1, "(" RECV_VAR "=(void *)(");

  if (recv_info->cls) {
    dcode = objc_get_dispatch_code(recv_info->cls, meth_name, recv_info->is_inst);
    uses_dtable = objc_main_opts.dtable && dcode >= 0 && !sealed;
  }

  sel_num = objc_get_sel_num(meth_name);
  bufsize = strlen(sig->imp_type) + 200;
  if (recv_info->cls && recv_info->cls->superclass)
    bufsize += strlen(recv_info->cls->superclass->name);
  repl = buf = ALLOC_N(char, bufsize); 
  buf += sprintf(buf, "), (*%s", sig->imp_type);

  if (recv_info->is_super) {
    if (uses_dtable)
      func = OBJC_DISPATCH_IMP_SUPER_FUNC;
    else if (objc_main_opts.inline_cache)
      func = OBJC_LOOKUP_IMP_SUPER_INLINE_FUNC;
    else
      func = OBJC_LOOKUP_IMP_SUPER_FUNC;
    buf += sprintf(buf, "%s(objc_lookup_class(\"%s\"), (id)self",
                   func, recv_info->cls->name);
  } else {
    if (uses_dtable)
      func = OBJC_DISPATCH_IMP_FUNC;
    else if (objc_main_opts.inline_cache)
      func = OBJC_LOOKUP_IMP_INLINE_FUNC;
    else
      func = OBJC_LOOKUP_IMP_FUNC;
    buf += sprintf(buf, "%s((id)" RECV_VAR, func);
  }

  if (uses_dtable)
    buf += sprintf(buf, ", %d", dcode);
  else {
    buf += sprintf(buf, ", (SEL)" SEL_TABLE_VAR "[%lu]", sel_num);
    if (objc_main_opts.inline_cache)
      buf += sprintf(buf, ", (InlineCache)&" IC_TABLE_VAR "[%lu]",
                     objc_nsend_sites++);
  }
  buf += sprintf(buf, "))((id)%s, (SEL)" SEL_TABLE_VAR "[%lu]",
                 recv_info->is_super ? "self" : RECV_VAR, sel_num);
  objc_add_patch(kw_lc, kw_len, repl);
  objc_add_patch(rb_lc, 1, "))");

  if (recv_info)
    recv_info = NULL;
}

static objc_method_sig_t
validate_receiver(const char *meth_name)
{
  objc_method_sig_t sig = NULL;
  objc_method_t meth;
  const char *supermsg = "";
  objc_str_list_t prtcs;
  objc_protocol_method_t prtc_meth;

  /* validate @class */
  if (!recv_info->cls && recv_info->is_declared) {
    parse_error("interfaces of class '%s' are not declared", recv_info->name);
    return NULL;
  }

  /* validate 'super' */
  if (recv_info->is_super) {
    supermsg = "superclass of ";

    if (!recv_info->cls) {
      parse_error("cannot use 'super' in root class");
      return NULL;
    } else if (!is_meth_def_scope) {
      parse_error("cannot use 'super' in outer method");
      return NULL;
    }
  }

  /* validate method */
  if (recv_info->cls) {
    meth = objc_class_get_method(recv_info->cls, meth_name, recv_info->is_inst);
    if (meth)
      sig = meth->sig;
    else {
      if (objc_main_opts.no_strict_msg) {
        parse_warn("%s`%s' may not respond to `%c%s'", supermsg,
                   recv_info->name, recv_info->is_inst ? '-' : '+', meth_name);
        sig = objc_get_global_method_sig(meth_name);
      }
    }
  } else
    sig = objc_get_global_method_sig(meth_name);

  /* validate protocols */
  if (!sig && recv_info->prtcs) {
    prtcs = recv_info->prtcs;
    while (prtcs) {
      prtc_meth = objc_protocol_get_method(objc_get_protocol(prtcs->car),
                                           meth_name, recv_info->is_inst);
      if (prtc_meth) {
        sig = prtc_meth->sig;
        break;
      } else
        prtcs = prtcs->cdr;
    }
  }

  if (sig)
    return sig;
  else {
    parse_error("cannot assert that %s`%s' responds to `%c%s'", supermsg,
                recv_info->name, recv_info->is_inst ? '-' : '+', meth_name);
    return NULL;
  }
}

static objc_method_sig_t
register_method_sig(objc_class_t cls,
                    const char *name,
                    objc_node_t dclr,
                    BOOL is_inst)
{
  objc_method_sig_t sig;

  /* register global method signature */
  if (!objc_register_global_method_sig(name, dclr))
    goto error;

  /* register local method signature */
  objc_register_sel_name(name, YES, NO);
  sig = objc_class_register_method_sig(cls, name, dclr, is_inst);
  if (sig)
    return sig;

error:
  parse_error("incompatible method declaration `%c[%s %s]'",
              dclr->a.mth_dclr.mth_spcr->a.mth_spcr.rtype ? '-' : '+',
              objc_current_class->name, name);
  return NULL;
}

static objc_method_sig_t
register_protocol_method_sig(objc_protocol_t prtc,
                             const char *name,
                             objc_node_t dclr,
                             BOOL is_inst)
{
  objc_method_sig_t sig;

  /* register global method signature */
  if (!objc_register_global_method_sig(name, dclr))
    goto error;

  /* register local method signature */
  objc_register_sel_name(name, YES, NO);
  sig = objc_protocol_register_method_sig(prtc, name, dclr, is_inst);
  if (sig)
    return sig;

error:
  parse_error("incompatible method declaration `%c[%s %s]'",
              dclr->a.mth_dclr.mth_spcr->a.mth_spcr.rtype ? '-' : '+',
              prtc->name, name);
  return NULL;
}

static void
add_direct_binding_imp_patches(size_t lb_lc, size_t rb_lc,
                               size_t kw_lc, size_t kw_len,
                               objc_method_t meth)
{
  char *repl;

  repl = ALLOC_N(char, strlen(meth->imp) + 50);
  sprintf(repl, "%s(", meth->imp);
  objc_add_patch(lb_lc, 1, repl);

  sprintf(repl, ", (SEL)" SEL_TABLE_VAR "[%lu]",
          objc_get_sel_num(meth->sig->name));
  objc_add_patch(kw_lc, kw_len, repl);

  objc_add_patch(rb_lc, 1, ")");
}

static void
validate_class_type(struct class_validation_result *res,
                    BOOL is_inst_meth,
                    objc_scope_t sc,
                    const char *target_type,
                    objc_node_t expr)
{
  char *expr_type;
  objc_class_t ctxt_cls;
  objc_ctype_info_t target_info, expr_info;

  res->name = NULL;
  ctxt_cls = objc_scope_get_class(sc);
  expr_type = objc_ctype_from_expr(sc, expr);
  if (*target_type == OBJC_CTYPE_ELLIPSIS_CODE)
    res->type = VARIABLE_ARG;
  else if (objc_ctype_is_object(target_type)) {
    if (objc_ctype_is_object(expr_type)) {
      target_info = objc_ctype_get_info(target_type, ctxt_cls, is_inst_meth);
      expr_info = objc_ctype_get_info(expr_type, ctxt_cls, is_inst_meth);

      /* 'Instance' and 'Self' special types */
      if (*target_type == OBJC_CTYPE_INSTANCE_CODE ||
          *target_type == OBJC_CTYPE_XSELF_CODE)
        res->type = OBJECT;

      /* same class */
      else if (target_info->cls == expr_info->cls) {
        res->type = SAME_CLASS;
        if (*target_type == OBJC_CTYPE_ID_CODE) {
          target_info = objc_ctype_get_info(target_type, ctxt_cls, is_inst_meth);
          if (target_info->name)
            res->name = objc_strdup(target_info->name);
        }

      } else {
        /* -id and any-class */
        if (target_info->is_id) {
          res->type = OBJECT;

        } else if (target_info->is_inst) {
          /* -any-class and -any-class */
          if (expr_info->is_inst) {

            /* -any-class and -id */
            if (expr_info->is_id) {
              res->type = COMPATIBLE_CLASS;
              res->name = objc_strdup(target_info->name);

            /* same class */
            } else if (STREQ(target_info->name, expr_info->name)) {
              res->type = SAME_CLASS;
              res->name = objc_strdup(target_info->name);

            /* subclass */
            } else if (target_info->cls && expr_info->cls) {
              if (objc_class_is_kind_of(expr_info->cls, target_info->cls)) {
                res->type = COMPATIBLE_CLASS;
                res->name = objc_strdup(target_info->name);
              } else
                res->type = INCOMPATIBLE_CLASS;

            /* @class declared */
            } else if (target_info->is_declared || expr_info->is_declared) {
              res->type = UNDECLARED_INTERFACE_CLASSES;

            /* error */
            } else {
              res->type = UNKNOWN_CLASSES;
            }

          /* -any-class and +any-class */
          } else
            res->type = INCOMPATIBLE_CLASS;

        /* +any-class */
        } else {
          if (!expr_info->is_inst || expr_info->is_id)
            res->type = CLASS_OBJECT;
          else
            res->type = INCOMPATIBLE_CLASS;
        }
      }
    } else if (*target_type == OBJC_CTYPE_NIL_CODE ||
               *expr_type == OBJC_CTYPE_NIL_CODE)
      res->type = BOTH_NOT_OBJECT;
    else if (*expr_type == OBJC_CTYPE_GENERIC_PTR_CODE)
      res->type = GENERIC_PTR;
    else
      res->type = INCOMPATIBLE_TYPE;
  } else if (*expr_type == OBJC_CTYPE_NIL_CODE)
    res->type = BOTH_NOT_OBJECT;
  else if (*target_type == OBJC_CTYPE_GENERIC_PTR_CODE)
    res->type = GENERIC_PTR;
  else if (objc_ctype_is_object(expr_type))
    res->type = INCOMPATIBLE_TYPE;
  else
    res->type = BOTH_NOT_OBJECT;
}

static void
add_class_cast_patch(struct class_validation_result *res, size_t lc,
                     const char *place)
{
  char *cast;

  switch (res->type) {
  case OBJECT:
    objc_add_patch(lc, 0, "(id)");
    break;

  case CLASS_OBJECT:
    objc_add_patch(lc, 0, "(Class)");
    break;

  case COMPATIBLE_CLASS:
  case SAME_CLASS:
    if (res->name) {
      cast = ALLOC_N(char, strlen(res->name)+12);
      sprintf(cast, "(%s *)", res->name);
      objc_add_patch(lc, 0, cast);
    }
    break;

  case VARIABLE_ARG:
  case GENERIC_PTR:
  case INCOMPATIBLE_TYPE:
    /* ignore */
    break;

  case INCOMPATIBLE_CLASS:
    parse_error("incompatible class in %s", place);
    break;

  case UNDECLARED_INTERFACE_CLASSES:
    parse_error("undeclared interface classes in %s", place);
    break;

  case UNKNOWN_CLASSES:
    parse_error("unknown classes in %s", place);
    break;
  }
}

enum {
  OBJC_STR_BEGIN_STR,
  OBJC_STR_BODY,
  OBJC_STR_ESCAPE
};

static char *
scan_objc_str(size_t lc, const char *orig)
{
  const char *bak;
  char *s, *buf;
  int st = OBJC_STR_BEGIN_STR;
  size_t i;

  bak = orig;
  buf = s = ALLOC_N(char, strlen(orig));
  while (*orig) {
    switch (*orig) {
    case '@':
      switch (st) {
      case OBJC_STR_BEGIN_STR:
        while (*orig != '"') orig++;
        st = OBJC_STR_BODY;
        break;
      default:
        *buf++ = *orig;
        break;
      }
      break;
    case '"':
      switch (st) {
      case OBJC_STR_ESCAPE:
        *buf++ = *orig;
        break;
      case OBJC_STR_BODY:
        st = OBJC_STR_BEGIN_STR;
        break;
      default:
        *buf++ = *orig;
        break;
      }
      break;
    case '\\':
      switch (st) {
      case OBJC_STR_ESCAPE:
        *buf++ = *orig;
        *buf++ = *orig;
        break;
      default:
        st = OBJC_STR_ESCAPE;
        break;
      }
      break;
    default:
      *buf++ = *orig;
      break;
    }
    orig++;
  };
  *buf = 0;

  /* add patches */
  orig = bak;
  for (i = 1; i < strlen(orig); i++) {
    if (!isspace(orig[i]))
      objc_add_patch(lc+i, 1, " ");
  }

  return s;
}

static void
add_rest_patches()
{
  char *repl, *buf;
  objc_str_list_t strs;

  repl = ALLOC_N(char, 64 + objc_all_objc_strs_len + objc_nobjc_strs * 32);

  /* objc constant string table */
  if (objc_nobjc_strs > 0) {
    buf = repl;
    buf += sprintf(buf, "}; static struct objc_const_str "
                   OBJC_STR_TABLE_VAR "[%d] = {", objc_nobjc_strs);
    strs = objc_objc_strs;
    while (strs) {
      buf += sprintf(buf, "{0, \"%s\", %lu}", strs->car, strlen(strs->car));
      strs = strs->cdr;
      if (strs)
        buf += sprintf(buf, ",");
    }
    sprintf(buf, "}");
    objc_add_patch(objc_str_table_lc, 1, repl);
  }

  /* selector table */
  if (objc_used_sel_num > 0) {
    sprintf(repl, "; static SEL " SEL_TABLE_VAR "[%lu];", objc_used_sel_num);
    objc_add_patch(sel_table_lc, 1, repl);
  }

  /* inline cache table */
  if (objc_main_opts.inline_cache && objc_nsend_sites > 0) {
    sprintf(repl, "}; static struct objc_inline_cache "
            IC_TABLE_VAR "[%lu]", objc_nsend_sites);
    objc_add_patch(ic_table_lc, 1, repl);
  }
}

